added histograms to fancy imshow and colormixer.

Unfortunately im suffering from circular references when the window is
initializing. It doesnt crash, but it throws a few exceptions...
A refactor is in order.
This commit is contained in:
sccolbert
2009-11-06 02:24:03 +01:00
parent 93fb7de121
commit efbaa849dd
3 changed files with 247 additions and 3 deletions
+62
View File
@@ -464,6 +464,68 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img,
img[i, j, 1] = <np.uint8_t>RGB[1]
img[i, j, 2] = <np.uint8_t>RGB[2]
@cython.boundscheck(False)
def histograms(np.ndarray[np.uint8_t, ndim=3] img, int nbins):
'''Calculate the channel histograms of the current image.
Parameters
----------
img : ndarray, uint8, ndim=3
The image to calculate the histogram.
nbins : int
The number of bins.
Returns
-------
out : (rcounts, gcounts, bcounts, vcounts)
The binned histograms of the RGB channels and grayscale intensity.
This is a NAIVE histogram routine, meant primarily for fast display.
'''
cdef int width = img.shape[1]
cdef int height = img.shape[0]
cdef np.ndarray[np.int32_t, ndim=1] r
cdef np.ndarray[np.int32_t, ndim=1] g
cdef np.ndarray[np.int32_t, ndim=1] b
cdef np.ndarray[np.int32_t, ndim=1] v
r = np.zeros((nbins,), dtype=np.int32)
g = np.zeros((nbins,), dtype=np.int32)
b = np.zeros((nbins,), dtype=np.int32)
v = np.zeros((nbins,), dtype=np.int32)
cdef int i, j, k, rbin, gbin, bbin, vbin
cdef float bin_width = 255./ nbins
cdef np.uint8_t R, G, B, V
for i in range(height):
for j in range(width):
R = img[i, j, 0]
G = img[i, j, 1]
B = img[i, j, 2]
V = <np.uint8_t> (0.3 * R + 0.59 * G + 0.11 * B)
rbin = <int>(R / bin_width)
gbin = <int>(G / bin_width)
bbin = <int>(B / bin_width)
vbin = <int>(V / bin_width)
if rbin == nbins:
rbin -= 1
if gbin == nbins:
gbin -= 1
if bbin == nbins:
gbin -= 1
if vbin == nbins:
vbin -= 1
r[rbin] += 1
g[gbin] += 1
b[bbin] += 1
v[vbin] += 1
return (r, g, b, v)
+165 -2
View File
@@ -14,7 +14,8 @@ except GuiLockError, gle:
else:
try:
from PyQt4.QtGui import (QApplication, QMainWindow, QImage, QPixmap,
QLabel, QWidget, QVBoxLayout, QSlider)
QLabel, QWidget, QVBoxLayout, QSlider,
QPainter, QColor, QFrame)
from PyQt4 import QtCore, QtGui
except ImportError:
@@ -465,6 +466,121 @@ else:
self.v_value.setText(str(v)[:5])
class Histogram(QWidget):
'''A Class which draws a scaling histogram in
a widget.
The argument to the constructor 'vals' is a list of tuples
of the following form:
vals = [(counts, colormap)]
where counts are the bin values in the histogram
and colormap is a tuple of (R, G, B) tuples the same length
as counts. These are the colors to apply to the histogram bars.
Colormap can also contain a single tuple, in which case this is
the color applied to all bars of that histogram.
Each histogram is drawn in order from left to right in its own
box and the values are scaled so that max(count) = height.
This is a linear scaling.
The histogram assumes the bins were evenly spaced.
'''
def __init__(self, counts, colormap):
QWidget.__init__(self)
self._validate_input(counts, colormap)
self.counts = counts
self.n = np.sum(self.counts)
self.colormap = colormap
self.setMinimumSize(100, 50)
def _validate_input(self, counts, colormap):
if len(counts) != len(colormap):
if len(colormap) != 1:
msg = 'Colormap must be same length as count or 1'
raise ValueError(msg)
def paintEvent(self, evt):
# get the widget dimensions
orig_width = self.width()
orig_height = self.height()
# fill perc % of the widget
perc = 1.0
width = int(orig_width * perc)
height = int(orig_height * perc)
# get the starting origin
x_orig = int((orig_width - width) / 2)
# we want to start at the bottom and draw up.
y_orig = orig_height - int((orig_height - height) / 2)
# a running x-position
running_pos = x_orig
# calculate to number of bars
nbars = len(self.counts)
# calculate the bar widths, this compilcation is
# necessary because integer trunction severly cripples
# the layout.
remainder = width % nbars
bar_width = [int(width / nbars)] * nbars
for i in range(remainder):
bar_width[i]+=1
paint = QPainter()
paint.begin(self)
if len(self.colormap) == 1:
self.colormap = self.colormap * len(self.counts)
# determine the scaling factor
max_val = np.max(self.counts)
scale = 1. * height / max_val
# draw the bars for this graph
for i in range(len(self.counts)):
bar_height = self.counts[i] * scale
r, g, b = self.colormap[i]
paint.setPen(QColor(r, g, b))
paint.setBrush(QColor(r, g, b))
paint.drawRect(running_pos, y_orig, bar_width[i], -bar_height)
running_pos += bar_width[i]
paint.end()
def update_hist(self, counts, cmap):
self._validate_input(counts, cmap)
self.counts = counts
self.colormap = cmap
self.repaint()
class MultiHist(QFrame):
def __init__(self, vals):
QFrame.__init__(self)
self.hists = []
for counts, cmap in vals:
self.hists.append(Histogram(counts, cmap))
self.layout = QtGui.QGridLayout(self)
for i in range(len(self.hists))[::-1]:
self.layout.addWidget(self.hists[i], i, 0)
def update_hists(self, vals):
for i in range(len(vals)):
counts, cmap = vals[i]
self.hists[i].update_hist(counts, cmap)
class FancyImageWindow(ImageWindow):
def __init__(self, arr, mgr):
ImageWindow.__init__(self, arr, mgr)
@@ -475,9 +591,13 @@ else:
self.label.setMinimumSize(QtCore.QSize(100, 100))
self.mixer_panel = MixerPanel(self.arr, self.refresh_image)
self.layout.addWidget(self.mixer_panel, 0, 1, 2, 1)
self.layout.addWidget(self.mixer_panel, 0, 2)
self.mixer_panel.show()
self.rgb_hist = MultiHist(self.calc_hist())
self.layout.addWidget(self.rgb_hist, 0, 1)
self.rgb_hist.show()
self.rgb_hsv_disp = RGBHSVDisplay()
self.layout.addWidget(self.rgb_hsv_disp, 1, 0)
self.rgb_hsv_disp.show()
@@ -485,9 +605,52 @@ else:
self.layout.setColumnStretch(0, 1)
self.layout.setRowStretch(0, 1)
# hook up the mixer sliders move events to trigger a
# histogram redraw.
self.mixer_panel.rgb_add_sliders.sliders['R'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.rgb_add_sliders.sliders['G'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.rgb_add_sliders.sliders['B'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.rgb_mul_sliders.sliders['R'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.rgb_mul_sliders.sliders['G'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.rgb_mul_sliders.sliders['B'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_add_sliders.sliders['H'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_add_sliders.sliders['S'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_add_sliders.sliders['V'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_mul_sliders.sliders['H'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_mul_sliders.sliders['S'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.hsv_mul_sliders.sliders['V'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.bright_sliders.sliders['+'].\
sliderMoved.connect(self.update_histogram)
self.mixer_panel.bright_sliders.sliders['x'].\
sliderMoved.connect(self.update_histogram)
def update_histogram(self):
self.rgb_hist.update_hists(self.calc_hist())
def calc_hist(self):
rvals, gvals, bvals, grays = \
self.mixer_panel.mixer.histograms(100)
vals = ((rvals, ((255,0,0),)),(gvals, ((0,255,0),)),
(bvals, ((0,0,255),)), (grays, ((0, 0, 0),)))
return vals
def refresh_image(self):
pm = QPixmap.fromImage(self.label.img)
self.label.setPixmap(pm)
self.update_histogram()
def scale_mouse_pos(self, x, y):
width = self.label.width()
+20 -1
View File
@@ -339,4 +339,23 @@ class ColorMixer(object):
'''
R, G, B = _colormixer.py_hsv_2_rgb(H, S, V)
return (R, G, B)
return (R, G, B)
def histograms(self, nbins):
'''Calculate the channel histograms of the current image.
Parameters
----------
nbins : int
The number of bins.
Returns
-------
out : (rcounts, gcounts, bcounts, vcounts)
The binned histograms of the RGB channels and grayscale intensity.
This is a NAIVE histogram routine, meant primarily for fast display.
'''
return _colormixer.histograms(self.img, nbins)