mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-20 12:40:31 +08:00
Finished the refactor of the mixer and the histogram.
Performance is now much better, a bunch of bugs are gone, and it shouldnt throw anymore exceptions.
This commit is contained in:
@@ -529,68 +529,7 @@ 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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
import cython
|
||||
|
||||
|
||||
cdef inline float tri_max(float a, float b, float c):
|
||||
cdef float MAX
|
||||
|
||||
if a > b:
|
||||
MAX = a
|
||||
else:
|
||||
MAX = b
|
||||
|
||||
if MAX > c:
|
||||
return MAX
|
||||
else:
|
||||
return c
|
||||
|
||||
|
||||
@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 float R, G, B, V
|
||||
|
||||
for i in range(height):
|
||||
for j in range(width):
|
||||
R = <float>img[i, j, 0]
|
||||
G = <float>img[i, j, 1]
|
||||
B = <float>img[i, j, 2]
|
||||
V = tri_max(R, G, B)
|
||||
|
||||
rbin = <int>(R / bin_width)
|
||||
gbin = <int>(G / bin_width)
|
||||
bbin = <int>(B / bin_width)
|
||||
vbin = <int>(V / bin_width)
|
||||
|
||||
# fully open last bin
|
||||
if R == 255:
|
||||
rbin -= 1
|
||||
if G == 255:
|
||||
gbin -= 1
|
||||
if B == 255:
|
||||
bbin -= 1
|
||||
if V == 255:
|
||||
vbin -= 1
|
||||
|
||||
r[rbin] += 1
|
||||
g[gbin] += 1
|
||||
b[bbin] += 1
|
||||
v[vbin] += 1
|
||||
|
||||
return (r, g, b, v)
|
||||
@@ -6,8 +6,6 @@ from PyQt4.QtGui import (QWidget, QStackedWidget, QSlider, QVBoxLayout,
|
||||
from util import ColorMixer
|
||||
|
||||
|
||||
|
||||
|
||||
class IntelligentSlider(QWidget):
|
||||
''' A slider that adds a 'name' attribute and calls a callback
|
||||
with 'name' as an argument to the registerd callback.
|
||||
@@ -26,6 +24,7 @@ class IntelligentSlider(QWidget):
|
||||
self.callback = callback
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.manually_triggered = False
|
||||
|
||||
self.slider = QSlider()
|
||||
self.slider.setRange(0, 1000)
|
||||
@@ -49,15 +48,19 @@ class IntelligentSlider(QWidget):
|
||||
def slider_changed(self, val):
|
||||
val = self.val()
|
||||
self.value_label.setText(str(val)[:4])
|
||||
self.callback(self.name, val)
|
||||
|
||||
if not self.manually_triggered:
|
||||
self.callback(self.name, val)
|
||||
|
||||
def set_conv_fac(self, a, b):
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
def set_value(self, val):
|
||||
self.manually_triggered = True
|
||||
self.slider.setValue(int((val - self.b) / self.a))
|
||||
self.value_label.setText(str(val)[:4])
|
||||
self.manually_triggered = False
|
||||
|
||||
def val(self):
|
||||
return self.slider.value() * self.a + self.b
|
||||
@@ -227,13 +230,19 @@ class MixerPanel(QWidget):
|
||||
|
||||
def combo_box_changed(self, index):
|
||||
self.sliders.setCurrentIndex(index)
|
||||
self.reset_sliders()
|
||||
self.reset()
|
||||
|
||||
def rgb_radio_changed(self):
|
||||
self.reset_sliders()
|
||||
self.reset()
|
||||
|
||||
def hsv_radio_changed(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.reset_sliders()
|
||||
self.mixer.set_to_stateimg()
|
||||
if self.callback:
|
||||
self.callback()
|
||||
|
||||
def reset_sliders(self):
|
||||
# handle changing the conversion factors necessary
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import numpy as np
|
||||
|
||||
from PyQt4.QtGui import QWidget, QPainter, QGridLayout, QColor
|
||||
|
||||
from util import histograms
|
||||
|
||||
|
||||
class ColorHistogram(QWidget):
|
||||
'''A Class which draws a scaling histogram in
|
||||
a widget.
|
||||
|
||||
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 (R, G, B), in which case this is
|
||||
the color applied to all bars of that histogram.
|
||||
|
||||
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) != 3:
|
||||
msg = '''Colormap must be a list of 3-tuples the same
|
||||
length as counts or a 3-tuple'''
|
||||
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
|
||||
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)
|
||||
|
||||
# determine the scaling factor
|
||||
max_val = np.max(self.counts)
|
||||
scale = 1. * height / max_val
|
||||
|
||||
# determine if we have a colormap and drop into the appopriate
|
||||
# loop.
|
||||
if hasattr(self.colormap[0], '__iter__'):
|
||||
# assume we have a colormap
|
||||
for i in range(len(self.counts)):
|
||||
bar_height = self.counts[i]
|
||||
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]
|
||||
|
||||
else:
|
||||
# we have a tuple
|
||||
r, g, b = self.colormap
|
||||
paint.setPen(QColor(r, g, b))
|
||||
paint.setBrush(QColor(r, g, b))
|
||||
for i in range(len(self.counts)):
|
||||
bar_height = self.counts[i] * scale
|
||||
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 QuadHistogram(QWidget):
|
||||
'''A class which uses ColorHistogram to draw
|
||||
the 4 histograms of an image. R, G, B, and Value.
|
||||
|
||||
The 4 histograms are layout out in a grid,
|
||||
and can be specified horizontal or vertical,
|
||||
and in which order ie. ['R', 'G', 'B', 'V']
|
||||
'''
|
||||
|
||||
def __init__(self, img, layout='vertical', order=['R', 'G', 'B', 'V']):
|
||||
QWidget.__init__(self)
|
||||
r, g, b, v = histograms(img, 100)
|
||||
self.r_hist = ColorHistogram(r, (255, 0, 0))
|
||||
self.g_hist = ColorHistogram(g, (0, 255, 0))
|
||||
self.b_hist = ColorHistogram(b, (0, 0, 255))
|
||||
self.v_hist = ColorHistogram(v, (0, 0, 0))
|
||||
|
||||
self.layout = QGridLayout(self)
|
||||
|
||||
order_map = {'R': self.r_hist, 'G': self.g_hist, 'B': self.b_hist,
|
||||
'V': self.v_hist}
|
||||
|
||||
if layout=='vertical':
|
||||
for i in range(len(order)):
|
||||
self.layout.addWidget(order_map[order[i]], i, 0)
|
||||
elif layout=='horizontal':
|
||||
for i in range(len(order)):
|
||||
self.layout.addWidget(order_map[order[i]], 0, i)
|
||||
|
||||
def update_hists(self, img):
|
||||
r, g, b, v = histograms(img, 100)
|
||||
self.r_hist.update_hist(r, (255, 0, 0))
|
||||
self.g_hist.update_hist(g, (0, 255, 0))
|
||||
self.b_hist.update_hist(b, (0, 0, 255))
|
||||
self.v_hist.update_hist(v, (0, 0, 0))
|
||||
@@ -18,6 +18,7 @@ else:
|
||||
QPainter, QColor, QFrame)
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from q_color_mixer import MixerPanel
|
||||
from q_histogram import QuadHistogram
|
||||
|
||||
except ImportError:
|
||||
print 'PyQT4 libraries not installed. Plugin not loaded.'
|
||||
@@ -124,121 +125,6 @@ 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)
|
||||
@@ -253,9 +139,9 @@ else:
|
||||
self.mixer_panel.show()
|
||||
self.mixer_panel.set_callback(self.refresh_image)
|
||||
|
||||
self.rgb_hist = MultiHist(self.calc_hist())
|
||||
self.layout.addWidget(self.rgb_hist, 0, 1)
|
||||
self.rgb_hist.show()
|
||||
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)
|
||||
@@ -273,21 +159,13 @@ else:
|
||||
self.layout.addWidget(self.save_file, 1, 2)
|
||||
|
||||
|
||||
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 update_histograms(self):
|
||||
self.rgbv_hist.update_hists(self.arr)
|
||||
|
||||
def refresh_image(self):
|
||||
pm = QPixmap.fromImage(self.label.img)
|
||||
self.label.setPixmap(pm)
|
||||
self.update_histogram()
|
||||
self.update_histograms()
|
||||
|
||||
def scale_mouse_pos(self, x, y):
|
||||
width = self.label.width()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import numpy as np
|
||||
import _colormixer
|
||||
import _histograms
|
||||
# utilities to make life easier for plugin writers.
|
||||
|
||||
|
||||
@@ -151,6 +152,26 @@ def prepare_for_display(npy_img):
|
||||
return out
|
||||
|
||||
|
||||
def histograms(img, nbins):
|
||||
'''Calculate the channel histograms of the current image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : ndarray, ndim=3, dtype=np.uint8
|
||||
nbins : int
|
||||
The number of bins.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : (rcounts, gcounts, bcounts, vcounts)
|
||||
The binned histograms of the RGB channels and intensity values.
|
||||
|
||||
This is a NAIVE histogram routine, meant primarily for fast display.
|
||||
|
||||
'''
|
||||
|
||||
return _histograms.histograms(img, nbins)
|
||||
|
||||
|
||||
class ColorMixer(object):
|
||||
''' a class to manage mixing colors in an image.
|
||||
@@ -347,21 +368,3 @@ class ColorMixer(object):
|
||||
R, G, B = _colormixer.py_hsv_2_rgb(H, S, V)
|
||||
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)
|
||||
@@ -15,12 +15,17 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
# This function tries to create C files from the given .pyx files. If
|
||||
# it fails, we build the checked-in .c files.
|
||||
cython(['_plugins/_colormixer.pyx'], working_path=base_path)
|
||||
cython(['_plugins/_colormixer.pyx', '_plugins/_histograms.pyx'],
|
||||
working_path=base_path)
|
||||
|
||||
config.add_extension('_plugins._colormixer',
|
||||
sources=['_plugins/_colormixer.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
|
||||
config.add_extension('_plugins._histograms',
|
||||
sources=['_plugins/_histograms.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
|
||||
return config
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user