mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-27 21:53:30 +08:00
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:
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user