Added tests for colormixer and histograms. Moved tests up a directory level because

nosetests doesnt see tests in a directory beginning with an _ (_plugins/).
Moved fancy imshow to its own module scivi.py
This commit is contained in:
sccolbert
2009-11-08 02:40:12 +01:00
parent ec5bba7682
commit a3737fa34c
8 changed files with 411 additions and 223 deletions
+2 -2
View File
@@ -10,8 +10,8 @@
- Mahipal Raythattha
Documentation infrastructure
- Chris Colbert
OpenCV wrappers
- S. Chris Colbert
OpenCV wrappers, Scivi, Qt and Gtk gui bits.
- Holger Rapp
OpenCV functions and better OSX library loader
+1 -1
View File
@@ -1,4 +1,4 @@
[qt]
description = Fast image display using the Qt library
provides = imshow, _app_show
provides = imshow, _app_show, imsave
+22 -171
View File
@@ -1,5 +1,5 @@
from util import prepare_for_display, window_manager, GuiLockError
from textwrap import dedent
import numpy as np
import sys
@@ -14,12 +14,8 @@ except GuiLockError, gle:
else:
try:
from PyQt4.QtGui import (QApplication, QMainWindow, QImage, QPixmap,
QLabel, QWidget, QVBoxLayout, QSlider,
QPainter, QColor, QFrame, QLayoutItem)
QLabel, QWidget)
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt
from q_color_mixer import MixerPanel
from q_histogram import QuadHistogram
except ImportError:
print 'PyQT4 libraries not installed. Plugin not loaded.'
@@ -29,10 +25,10 @@ else:
app = None
class LabelImage(QLabel):
class ImageLabel(QLabel):
def __init__(self, parent, arr):
QLabel.__init__(self)
self.parent = parent
# we need to hold a reference to
# arr because QImage doesn't copy the data
# and the buffer must be alive as long
@@ -47,14 +43,10 @@ else:
self.img = QImage(arr.data, arr.shape[1], arr.shape[0],
arr.strides[0], QImage.Format_RGB888)
self.pm = QPixmap.fromImage(self.img)
self.setAlignment(Qt.AlignTop)
self.setPixmap(self.pm)
self.setAlignment(QtCore.Qt.AlignTop)
self.setMinimumSize(100, 100)
def mouseMoveEvent(self, evt):
self.parent.label_mouseMoveEvent(evt)
def resizeEvent(self, evt):
width = self.width()
pm = QPixmap.fromImage(self.img)
@@ -62,22 +54,16 @@ else:
self.setPixmap(self.pm)
def update_image(self):
width = self.width()
pm = QPixmap.fromImage(self.img)
pm = pm.scaledToWidth(width)
self.setPixmap(pm)
class ImageWindow(QMainWindow):
def __init__(self, arr, mgr):
QMainWindow.__init__(self)
self.setWindowTitle('scikits.image')
self.mgr = mgr
self.main_widget = QWidget()
self.layout = QtGui.QGridLayout(self.main_widget)
self.setCentralWidget(self.main_widget)
self.label = LabelImage(self, arr)
self.label = ImageLabel(self, arr)
self.layout.addWidget(self.label, 0, 0)
self.layout.addLayout
self.mgr.add_window(self)
@@ -88,156 +74,6 @@ else:
# references to it
self.mgr.remove_window(self)
def label_mouseMoveEvent(self, evt):
pass
class RGBHSVDisplay(QWidget):
def __init__(self):
QWidget.__init__(self)
self.posx_label = QLabel('X-pos:')
self.posx_value = QLabel()
self.posy_label = QLabel('Y-pos:')
self.posy_value = QLabel()
self.r_label = QLabel('R:')
self.r_value = QLabel()
self.g_label = QLabel('G:')
self.g_value = QLabel()
self.b_label = QLabel('B:')
self.b_value = QLabel()
self.h_label = QLabel('H:')
self.h_value = QLabel()
self.s_label = QLabel('S:')
self.s_value = QLabel()
self.v_label = QLabel('V:')
self.v_value = QLabel()
self.layout = QtGui.QGridLayout(self)
self.layout.addWidget(self.posx_label, 0, 0)
self.layout.addWidget(self.posx_value, 0, 1)
self.layout.addWidget(self.posy_label, 1, 0)
self.layout.addWidget(self.posy_value, 1, 1)
self.layout.addWidget(self.r_label, 0, 2)
self.layout.addWidget(self.r_value, 0, 3)
self.layout.addWidget(self.g_label, 1, 2)
self.layout.addWidget(self.g_value, 1, 3)
self.layout.addWidget(self.b_label, 2, 2)
self.layout.addWidget(self.b_value, 2, 3)
self.layout.addWidget(self.h_label, 0, 4)
self.layout.addWidget(self.h_value, 0, 5)
self.layout.addWidget(self.s_label, 1, 4)
self.layout.addWidget(self.s_value, 1, 5)
self.layout.addWidget(self.v_label, 2, 4)
self.layout.addWidget(self.v_value, 2, 5)
def update_vals(self, data):
xpos, ypos, r, g, b, h, s, v = data
self.posx_value.setText(str(xpos)[:5])
self.posy_value.setText(str(ypos)[:5])
self.r_value.setText(str(r)[:5])
self.g_value.setText(str(g)[:5])
self.b_value.setText(str(b)[:5])
self.h_value.setText(str(h)[:5])
self.s_value.setText(str(s)[:5])
self.v_value.setText(str(v)[:5])
class FancyImageWindow(ImageWindow):
def __init__(self, arr, mgr):
ImageWindow.__init__(self, arr, mgr)
self.arr = arr
self.label.setMouseTracking(True)
self.mixer_panel = MixerPanel(self.arr)
self.layout.addWidget(self.mixer_panel, 0, 2)
self.mixer_panel.show()
self.mixer_panel.set_callback(self.refresh_image)
self.rgbv_hist = QuadHistogram(self.arr)
self.layout.addWidget(self.rgbv_hist, 0, 1)
self.rgbv_hist.show()
self.rgb_hsv_disp = RGBHSVDisplay()
self.layout.addWidget(self.rgb_hsv_disp, 1, 0)
self.rgb_hsv_disp.show()
self.layout.setColumnStretch(0, 1)
self.layout.setRowStretch(0, 1)
self.save_file = QtGui.QPushButton('Save to File')
self.save_file.clicked.connect(self.save_to_file)
self.save_variable = QtGui.QPushButton('Save to Variable')
self.save_variable.clicked.connect(self.save_to_variable)
self.save_file.show()
self.save_variable.show()
self.layout.addWidget(self.save_variable, 1, 1)
self.layout.addWidget(self.save_file, 1, 2)
def update_histograms(self):
self.rgbv_hist.update_hists(self.arr)
def save_to_variable(self):
from scikits.image import io
from textwrap import dedent
img = self.arr.copy()
io.push(img)
msg = dedent('''
The image has been pushed to the io stack.
Use io.pop() to retrieve the most recently pushed image.''')
msglabel = QLabel(msg)
dialog = QtGui.QDialog()
ok = QtGui.QPushButton('OK', dialog)
ok.clicked.connect(dialog.accept)
ok.setDefault(True)
dialog.layout = QtGui.QGridLayout(dialog)
dialog.layout.addWidget(msglabel, 0, 0, 1, 3)
dialog.layout.addWidget(ok, 1, 1)
dialog.exec_()
def save_to_file(self):
from scikits.image import io
filename = str(QtGui.QFileDialog.getSaveFileName())
if len(filename) == 0:
return
io.imsave(filename, self.arr)
def refresh_image(self):
self.label.update_image()
self.update_histograms()
def scale_mouse_pos(self, x, y):
width = self.label.pm.width()
height = self.label.pm.height()
x_frac = 1. * x / width
y_frac = 1. * y / height
width = self.arr.shape[1]
height = self.arr.shape[0]
new_x = int(width * x_frac)
new_y = int(height * y_frac)
return(new_x, new_y)
def label_mouseMoveEvent(self, evt):
x = evt.x()
y = evt.y()
x, y = self.scale_mouse_pos(x, y)
# handle tracking out of array bounds
maxw = self.arr.shape[1]
maxh = self.arr.shape[0]
if x >= maxw or y >= maxh or x < 0 or y < 0:
r = g = b = h = s = v = ''
else:
r = self.arr[y,x,0]
g = self.arr[y,x,1]
b = self.arr[y,x,2]
h, s, v = self.mixer_panel.mixer.rgb_2_hsv_pixel(r, g, b)
self.rgb_hsv_disp.update_vals((x, y, r, g, b, h, s, v))
def imshow(arr, fancy=False):
global app
@@ -249,7 +85,8 @@ else:
if not fancy:
iw = ImageWindow(arr, window_manager)
else:
iw = FancyImageWindow(arr, window_manager)
from scivi import SciviImageWindow
iw = SciviImageWindow(arr, window_manager)
iw.show()
@@ -260,3 +97,17 @@ else:
app.exec_()
else:
print 'No images to show. See `imshow`.'
def imsave(filename, img):
# we can support for other than 3D uint8 here...
img = prepare_for_display(img)
qimg = QImage(img.data, img.shape[1], img.shape[0],
img.strides[0], QImage.Format_RGB888)
saved = qimg.save(filename)
if not saved:
msg = dedent(
'''The image was not saved. Allowable file formats
for the QT imsave plugin are:
BMP, JPG, JPEG, PNG, PPM, TIFF, XBM, XPM''')
raise RuntimeError(msg)
+233
View File
@@ -0,0 +1,233 @@
'''
Scivi is written/maintained/developed by:
S. Chris Colbert - sccolbert@gmail.com
Scivi is free software and is part of the scikits.image project.
Scivi is governed by the licenses of the scikits.image project.
Please report any bugs to the author.
The scivi module is not meant to be used directly.
Use scikits.image.io.imshow(img, fancy=True)'''
from textwrap import dedent
import numpy as np
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import (QApplication, QMainWindow, QImage, QPixmap,
QLabel, QWidget, QVBoxLayout, QSlider,
QPainter, QColor, QFrame, QLayoutItem)
from q_color_mixer import MixerPanel
from q_histogram import QuadHistogram
class ImageLabel(QLabel):
def __init__(self, parent, arr):
QLabel.__init__(self)
self.parent = parent
# we need to hold a reference to
# arr because QImage doesn't copy the data
# and the buffer must be alive as long
# as the image is alive.
self.arr = arr
# we also need to pass in the row-stride to
# the constructor, because we can't guarantee
# that every row of the numpy data is
# 4-byte aligned. Which Qt would require
# if we didnt pass the stride.
self.img = QImage(arr.data, arr.shape[1], arr.shape[0],
arr.strides[0], QImage.Format_RGB888)
self.pm = QPixmap.fromImage(self.img)
self.setPixmap(self.pm)
self.setAlignment(QtCore.Qt.AlignTop)
self.setMinimumSize(100, 100)
self.setMouseTracking(True)
def mouseMoveEvent(self, evt):
self.parent.label_mouseMoveEvent(evt)
def resizeEvent(self, evt):
width = self.width()
pm = QPixmap.fromImage(self.img)
self.pm = pm.scaledToWidth(width)
self.setPixmap(self.pm)
def update_image(self):
width = self.width()
pm = QPixmap.fromImage(self.img)
pm = pm.scaledToWidth(width)
self.setPixmap(pm)
class RGBHSVDisplay(QWidget):
def __init__(self):
QWidget.__init__(self)
self.posx_label = QLabel('X-pos:')
self.posx_value = QLabel()
self.posy_label = QLabel('Y-pos:')
self.posy_value = QLabel()
self.r_label = QLabel('R:')
self.r_value = QLabel()
self.g_label = QLabel('G:')
self.g_value = QLabel()
self.b_label = QLabel('B:')
self.b_value = QLabel()
self.h_label = QLabel('H:')
self.h_value = QLabel()
self.s_label = QLabel('S:')
self.s_value = QLabel()
self.v_label = QLabel('V:')
self.v_value = QLabel()
self.layout = QtGui.QGridLayout(self)
self.layout.addWidget(self.posx_label, 0, 0)
self.layout.addWidget(self.posx_value, 0, 1)
self.layout.addWidget(self.posy_label, 1, 0)
self.layout.addWidget(self.posy_value, 1, 1)
self.layout.addWidget(self.r_label, 0, 2)
self.layout.addWidget(self.r_value, 0, 3)
self.layout.addWidget(self.g_label, 1, 2)
self.layout.addWidget(self.g_value, 1, 3)
self.layout.addWidget(self.b_label, 2, 2)
self.layout.addWidget(self.b_value, 2, 3)
self.layout.addWidget(self.h_label, 0, 4)
self.layout.addWidget(self.h_value, 0, 5)
self.layout.addWidget(self.s_label, 1, 4)
self.layout.addWidget(self.s_value, 1, 5)
self.layout.addWidget(self.v_label, 2, 4)
self.layout.addWidget(self.v_value, 2, 5)
def update_vals(self, data):
xpos, ypos, r, g, b, h, s, v = data
self.posx_value.setText(str(xpos)[:5])
self.posy_value.setText(str(ypos)[:5])
self.r_value.setText(str(r)[:5])
self.g_value.setText(str(g)[:5])
self.b_value.setText(str(b)[:5])
self.h_value.setText(str(h)[:5])
self.s_value.setText(str(s)[:5])
self.v_value.setText(str(v)[:5])
class SciviImageWindow(QMainWindow):
def __init__(self, arr, mgr):
QMainWindow.__init__(self)
self.arr = arr
self.mgr = mgr
self.main_widget = QWidget()
self.layout = QtGui.QGridLayout(self.main_widget)
self.setCentralWidget(self.main_widget)
self.label = ImageLabel(self, arr)
self.layout.addWidget(self.label, 0, 0)
self.layout.addLayout
self.mgr.add_window(self)
self.main_widget.show()
self.setWindowTitle('Scivi - The scikits.image viewer.')
self.mixer_panel = MixerPanel(self.arr)
self.layout.addWidget(self.mixer_panel, 0, 2)
self.mixer_panel.show()
self.mixer_panel.set_callback(self.refresh_image)
self.rgbv_hist = QuadHistogram(self.arr)
self.layout.addWidget(self.rgbv_hist, 0, 1)
self.rgbv_hist.show()
self.rgb_hsv_disp = RGBHSVDisplay()
self.layout.addWidget(self.rgb_hsv_disp, 1, 0)
self.rgb_hsv_disp.show()
self.layout.setColumnStretch(0, 1)
self.layout.setRowStretch(0, 1)
self.save_file = QtGui.QPushButton('Save to File')
self.save_file.clicked.connect(self.save_to_file)
self.save_stack = QtGui.QPushButton('Save to Stack')
self.save_stack.clicked.connect(self.save_to_stack)
self.save_file.show()
self.save_stack.show()
self.layout.addWidget(self.save_stack, 1, 1)
self.layout.addWidget(self.save_file, 1, 2)
def closeEvent(self, event):
# Allow window to be destroyed by removing any
# references to it
self.mgr.remove_window(self)
def update_histograms(self):
self.rgbv_hist.update_hists(self.arr)
def refresh_image(self):
self.label.update_image()
self.update_histograms()
def scale_mouse_pos(self, x, y):
width = self.label.pm.width()
height = self.label.pm.height()
x_frac = 1. * x / width
y_frac = 1. * y / height
width = self.arr.shape[1]
height = self.arr.shape[0]
new_x = int(width * x_frac)
new_y = int(height * y_frac)
return(new_x, new_y)
def label_mouseMoveEvent(self, evt):
x = evt.x()
y = evt.y()
x, y = self.scale_mouse_pos(x, y)
# handle tracking out of array bounds
maxw = self.arr.shape[1]
maxh = self.arr.shape[0]
if x >= maxw or y >= maxh or x < 0 or y < 0:
r = g = b = h = s = v = ''
else:
r = self.arr[y,x,0]
g = self.arr[y,x,1]
b = self.arr[y,x,2]
h, s, v = self.mixer_panel.mixer.rgb_2_hsv_pixel(r, g, b)
self.rgb_hsv_disp.update_vals((x, y, r, g, b, h, s, v))
def save_to_stack(self):
from scikits.image import io
img = self.arr.copy()
io.push(img)
msg = dedent('''
The image has been pushed to the io stack.
Use io.pop() to retrieve the most recently
pushed image.''')
msglabel = QLabel(msg)
dialog = QtGui.QDialog()
ok = QtGui.QPushButton('OK', dialog)
ok.clicked.connect(dialog.accept)
ok.setDefault(True)
dialog.layout = QtGui.QGridLayout(dialog)
dialog.layout.addWidget(msglabel, 0, 0, 1, 3)
dialog.layout.addWidget(ok, 1, 1)
dialog.exec_()
def save_to_file(self):
from scikits.image import io
filename = str(QtGui.QFileDialog.getSaveFileName())
if len(filename) == 0:
return
io.imsave(filename, self.arr)
@@ -1,49 +0,0 @@
from numpy.testing import *
import numpy as np
import scikits.image.io._plugins._colormixer as cm
class ColorMixerTest(object):
def setup(self):
self.state = np.ones((18, 33, 3), dtype=np.uint8) * 200
self.img = np.zeros_like(self.state)
def test_basic(self):
self.op(self.img, self.state, 0, self.positive)
assert_array_equal(self.img[..., 0],
self.py_op(self.state[..., 0], self.positive))
def test_clip(self):
self.op(self.img, self.state, 0, self.positive_clip)
assert_array_equal(self.img[..., 0],
np.ones_like(self.img[..., 0]) * 255)
def test_negative(self):
self.op(self.img, self.state, 0, self.negative)
assert_array_equal(self.img[..., 0],
self.py_op(self.state[..., 0], self.negative))
def test_negative_clip(self):
self.op(self.img, self.state, 0, self.negative_clip)
assert_array_equal(self.img[..., 0],
np.zeros_like(self.img[..., 0]))
class TestColorMixerAdd(ColorMixerTest):
op = cm.add
py_op = np.add
positive = 50
positive_clip = 56
negative = -50
negative_clip = -220
class TestColorMixerMul(ColorMixerTest):
op = cm.multiply
py_op = np.multiply
positive = 1.2
positive_clip = 2
negative = 0.5
negative_clip = -0.5
if __name__ == "__main__":
run_module_suite()
+1
View File
@@ -11,6 +11,7 @@ def configuration(parent_package='', top_path=None):
config = Configuration('io', parent_package, top_path)
config.add_data_dir('tests')
config.add_data_dir('_plugins/tests')
config.add_data_files('_plugins/*.ini')
# This function tries to create C files from the given .pyx files. If
+140
View File
@@ -0,0 +1,140 @@
from numpy.testing import *
import numpy as np
import scikits.image.io._plugins._colormixer as cm
class ColorMixerTest(object):
def setup(self):
self.state = np.ones((18, 33, 3), dtype=np.uint8) * 200
self.img = np.zeros_like(self.state)
def test_basic(self):
self.op(self.img, self.state, 0, self.positive)
assert_array_equal(self.img[..., 0],
self.py_op(self.state[..., 0], self.positive))
def test_clip(self):
self.op(self.img, self.state, 0, self.positive_clip)
assert_array_equal(self.img[..., 0],
np.ones_like(self.img[..., 0]) * 255)
def test_negative(self):
self.op(self.img, self.state, 0, self.negative)
assert_array_equal(self.img[..., 0],
self.py_op(self.state[..., 0], self.negative))
def test_negative_clip(self):
self.op(self.img, self.state, 0, self.negative_clip)
assert_array_equal(self.img[..., 0],
np.zeros_like(self.img[..., 0]))
class TestColorMixerAdd(ColorMixerTest):
op = cm.add
py_op = np.add
positive = 50
positive_clip = 56
negative = -50
negative_clip = -220
class TestColorMixerMul(ColorMixerTest):
op = cm.multiply
py_op = np.multiply
positive = 1.2
positive_clip = 2
negative = 0.5
negative_clip = -0.5
class TestColorMixerBright(object):
def setup(self):
self.state = np.ones((18, 33, 3), dtype=np.uint8) * 200
self.img = np.zeros_like(self.state)
def test_brightness_pos(self):
cm.brightness(self.img, self.state, 1.25, 1)
assert_array_equal(self.img, np.ones_like(self.img) * 251)
def test_brightness_neg(self):
cm.brightness(self.img, self.state, 0.5, -50)
assert_array_equal(self.img, np.ones_like(self.img) * 50)
def test_brightness_pos_clip(self):
cm.brightness(self.img, self.state, 2, 0)
assert_array_equal(self.img, np.ones_like(self.img) * 255)
def test_brightness_neg_clip(self):
cm.brightness(self.img, self.state, 0, 0)
assert_array_equal(self.img, np.zeros_like(self.img))
class TestColorMixer(object):
def setup(self):
self.state = np.ones((18, 33, 3), dtype=np.uint8) * 50
self.img = np.zeros_like(self.state)
def test_sigmoid(self):
import math
alpha = 1.5
beta = 1.5
c1 = 1 / (1 + math.exp(beta))
c2 = 1 / (1 + math.exp(beta - alpha)) - c1
state = self.state / 255.
cm.sigmoid_gamma(self.img, self.state, alpha, beta)
img = 1 / (1 + np.exp(beta - state * alpha))
img = np.asarray((img - c1) / c2 * 255, dtype='uint8')
assert_almost_equal(img, self.img)
def test_gamma(self):
gamma = 1.5
cm.gamma(self.img, self.state, gamma)
img = np.asarray(((self.state/255.)**(1/gamma))*255, dtype='uint8')
assert_array_almost_equal(img, self.img)
def test_rgb_2_hsv(self):
r = 255
g = 0
b = 0
h, s, v = cm.py_rgb_2_hsv(r, g, b)
assert_almost_equal(np.array([h]), np.array([0]))
assert_almost_equal(np.array([s]), np.array([1]))
assert_almost_equal(np.array([v]), np.array([1]))
def test_hsv_2_rgb(self):
h = 0
s = 1
v = 1
r, g, b = cm.py_hsv_2_rgb(h, s, v)
assert_almost_equal(np.array([r]), np.array([255]))
assert_almost_equal(np.array([g]), np.array([0]))
assert_almost_equal(np.array([b]), np.array([0]))
def test_hsv_add(self):
cm.hsv_add(self.img, self.state, 360, 0, 0)
assert_almost_equal(self.img, self.state)
def test_hsv_add_clip_neg(self):
cm.hsv_add(self.img, self.state, 0, 0, -1)
assert_equal(self.img, np.zeros_like(self.state))
def test_hsv_add_clip_pos(self):
cm.hsv_add(self.img, self.state, 0, 0, 1)
assert_equal(self.img, np.ones_like(self.state)*255)
def test_hsv_mul(self):
cm.hsv_multiply(self.img, self.state, 360, 1, 1)
assert_almost_equal(self.img, self.state)
def test_hsv_mul_clip_neg(self):
cm.hsv_multiply(self.img, self.state, 0, 0, 0)
assert_equal(self.img, np.zeros_like(self.state))
if __name__ == "__main__":
run_module_suite()
@@ -12,5 +12,17 @@ class TestHistogram:
for band in (r, g, b, v):
yield assert_equal, band.sum(), 50*50
def test_counts(self):
channel = np.arange(255).reshape(51, 5)
img = np.empty((51, 5, 3), dtype='uint8')
img[:,:,0] = channel
img[:,:,1] = channel
img[:,:,2] = channel
r, g, b, v = histograms(img, 255)
assert_array_equal(r, g)
assert_array_equal(r, b)
assert_array_equal(r, v)
assert_array_equal(r, np.ones(255))
if __name__ == "__main__":
run_module_suite()