added the beginnings of the color mixes, and improved fancy imshow for qt

This commit is contained in:
sccolbert
2009-11-04 02:28:57 +01:00
parent b00566a52c
commit 663738ab79
3 changed files with 220 additions and 13 deletions
+51
View File
@@ -0,0 +1,51 @@
# ColorMixer function implementations
import numpy as np
cimport numpy as np
import cython
@cython.boundscheck(False)
def add(np.ndarray[np.uint8_t, ndim=3] img,
np.ndarray[np.uint8_t, ndim=3] stateimg,
int channel, int ammount):
cdef int height = img.shape[0]
cdef int width = img.shape[1]
cdef int k = channel
cdef int n = ammount
cdef np.int16_t op_result
cdef int i, j
for i in range(height):
for j in range(width):
op_result = <np.int16_t>(stateimg[i,j,k] + n)
if op_result > 255:
img[i, j, k] = 255
elif op_result < 0:
img[i, j, k] = 0
else:
img[i, j, k] = <np.uint8_t>op_result
@cython.boundscheck(False)
def multiply(np.ndarray[np.uint8_t, ndim=3] img,
np.ndarray[np.uint8_t, ndim=3] stateimg,
int channel, float ammount):
cdef int height = img.shape[0]
cdef int width = img.shape[1]
cdef int k = channel
cdef float n = ammount
cdef float op_result
cdef int i, j
for i in range(height):
for j in range(width):
op_result = <float>(stateimg[i,j,k] * n)
if op_result > 255:
img[i, j, k] = 255
elif op_result < 0:
img[i, j, k] = 0
else:
img[i, j, k] = <np.uint8_t>op_result
+83 -12
View File
@@ -1,4 +1,4 @@
from util import prepare_for_display, window_manager, GuiLockError
from util import prepare_for_display, window_manager, GuiLockError, ColorMixer
import numpy as np
import sys
@@ -14,7 +14,8 @@ except GuiLockError, gle:
else:
try:
from PyQt4.QtGui import (QApplication, QMainWindow, QImage, QPixmap,
QLabel, QWidget, QVBoxLayout)
QLabel, QWidget, QVBoxLayout, QSlider)
from PyQt4 import QtCore, QtGui
except ImportError:
print 'PyQT4 libraries not installed. Plugin not loaded.'
@@ -53,9 +54,14 @@ else:
def __init__(self, arr, mgr):
QMainWindow.__init__(self)
self.mgr = mgr
self.main_widget = QWidget()
self.layout = QtGui.QHBoxLayout(self.main_widget)
self.setCentralWidget(self.main_widget)
self.label = LabelImage(self, arr)
self.setCentralWidget(self.label)
self.layout.addWidget(self.label)
self.mgr.add_window(self)
self.main_widget.show()
def closeEvent(self, event):
# Allow window to be destroyed by removing any
@@ -66,28 +72,93 @@ else:
pass
class SliderBlock(QWidget):
def __init__(self, srange, callback):
QWidget.__init__(self)
self.callback = callback
low = srange[0]
high = srange[1]
init = srange[2]
self.rslider = QSlider()
self.rslider.setRange(low, high)
self.rslider.setValue(init)
self.gslider = QSlider()
self.gslider.setRange(low, high)
self.gslider.setValue(init)
self.bslider = QSlider()
self.bslider.setRange(low, high)
self.bslider.setValue(init)
self.rslider.valueChanged.connect(self.rslider_changed)
self.gslider.valueChanged.connect(self.gslider_changed)
self.bslider.valueChanged.connect(self.bslider_changed)
self.layout = QtGui.QHBoxLayout(self)
self.layout.addWidget(self.rslider)
self.layout.addWidget(self.gslider)
self.layout.addWidget(self.bslider)
def rslider_changed(self, val):
self.callback('RED', val)
def gslider_changed(self, val):
self.callback('GREEN', val)
def bslider_changed(self, val):
self.callback('BLUE', val)
class FancyImageWindow(ImageWindow):
def __init__(self, arr, mgr):
ImageWindow.__init__(self, arr, mgr)
self.arr = arr
# for image manipulation
self.arrfloat = np.asarray(arr, dtype=np.float64)
self.arruint8 = arr.copy()
self.statusBar().showMessage('X: Y: ')
self.label.setScaledContents(True)
self.label.setMouseTracking(True)
self.mixer = ColorMixer(self.arr)
def keyPressEvent(self, evt):
self.arrfloat[:,:,0] *= 1.1
np.clip(self.arrfloat, 0, 255, self.arrfloat)
self.arr[:] = self.arrfloat[:]
self.sliders = SliderBlock((-255, 255, 0), self.svalueChanged)
self.msliders = SliderBlock((0, 1000, 500), self.mvalueChanged)
self.layout.addWidget(self.sliders)
self.layout.addWidget(self.msliders)
self.sliders.show()
self.msliders.show()
def svalueChanged(self, who, val):
if who == 'RED':
self.mixer.add(self.mixer.RED, val)
elif who == 'GREEN':
self.mixer.add(self.mixer.GREEN, val)
elif who == 'BLUE':
self.mixer.add(self.mixer.BLUE, val)
else:
return
pm = QPixmap.fromImage(self.label.img)
self.label.setPixmap(pm)
def mvalueChanged(self, who, val):
val = val / 500.
if who == 'RED':
self.mixer.multiply(self.mixer.RED, val)
elif who == 'GREEN':
self.mixer.multiply(self.mixer.GREEN, val)
elif who == 'BLUE':
self.mixer.multiply(self.mixer.BLUE, val)
else:
return
pm = QPixmap.fromImage(self.label.img)
self.label.setPixmap(pm)
print 'heard'
def scale_mouse_pos(self, x, y):
width = self.label.width()
+86 -1
View File
@@ -1,5 +1,5 @@
import numpy as np
import _colormixer
# utilities to make life easier for plugin writers.
@@ -149,3 +149,88 @@ def prepare_for_display(npy_img):
raise ValueError('Image must have 2 or 3 dimensions')
return out
class ColorMixer(object):
''' a class to manage mixing colors in an image.
The input array must be an RGB uint8 image.
The mixer maintains an original copy of the image,
and uses this copy to query the pixel data for operations.
It also makes a copy for sharing state across operations.
That is, if you add to a channel, and multiply to same channel,
the two operations are carried separately and the results
averaged together.
it modifies your array in place. This ensures that if you
bust over a threshold, you can always come back down.
The passed values to a function are always considered
absolute. Thus to threshold a channel completely you
can do mixer.add(RED, 255). Or to double the intensity
of the blue channel: mixer.multiply(BLUE, 2.)
To reverse these operations, respectively:
mixer.add(RED, 0), mixer.multiply(BLUE, 1.)
The majority of the backend is implemented in Cython,
so it should be quite quick.
'''
RED = 0
GREEN = 1
BLUE = 2
valid_channels = [RED, GREEN, BLUE]
def __init__(self, img):
if type(img) != np.ndarray:
raise ValueError('Image must be a numpy array')
if img.dtype != np.uint8:
raise ValueError('Image must have dtype uint8')
if img.ndim != 3 or img.shape[2] != 3:
raise ValueError('Image must be 3 channel MxNx3')
self.img = img
self.stateimg = img.copy()
def get_stateimage(self):
return self.stateimg
def commit_changes(self):
self.stateimg[:] = self.img[:]
def add(self, channel, ammount):
'''Add the specified ammount to the specified channel.
Parameters
----------
channel : flag
the color channel to operate on
RED, GREED, or BLUE
ammount : integer
the ammount of color to add to the channel,
can be positive or negative.
'''
assert channel in self.valid_channels
_colormixer.add(self.img, self.stateimg, channel, ammount)
def multiply(self, channel, ammount):
'''Mutliply the indicated channel by the specified value.
Parameters
----------
channel : flag
the color channel to operate on
RED, GREED, or BLUE
ammount : integer
the ammount of color to add to the channel,
can be positive or negative.
'''
assert channel in self.valid_channels
_colormixer.multiply(self.img, self.stateimg, channel, ammount)