mirror of
https://github.com/wassname/scikit-image.git
synced 2026-08-03 13:11:25 +08:00
added the beginnings of the color mixes, and improved fancy imshow for qt
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user