IA: Improved image properties dialog
[pyplotsuite.git] / pyplotsuite / imageanalyzer2.py
blob75cac13b56daba33e04e3fab80e1f539a7d98421
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Image Analizer -- A tool to visualize and analyze images
5 # Version: 0.1-alpha (see VERSION file)
6 # This program is part of PyPlotSuite <http://pyplotsuite.sourceforge.net>.
8 # Copyright (C) 2006-2007 Antonio Ingargiola <tritemio@gmail.com>
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 import sys
16 import os
17 import os.path
18 import imp
19 rootdir = os.path.abspath(imp.find_module('pyplotsuite')[1]) + '/'
20 sourcedir = rootdir + 'imageanalyzer/'
21 gladefile = sourcedir + 'image_analyzer.glade'
23 dark_suffix = '-buio'
24 debug = True
26 from PIL import Image
28 # Generic window description classes
29 from pyplotsuite.common.gtk_generic_windows import \
30 GenericMainPlotWindow, MyNavigationToolbar, GenericSecondaryWindow, \
31 DialogWindowWithCancel, GenericSaveFileDialog, ChildWindow
33 # Dialogs classes
34 from imageanalyzer.histogramdialog import HistogramApp
35 from imageanalyzer.sectionsdialog import SectionApp
37 # Mathematical functions
38 from numpy import arange, array, sqrt, log10, \
39 logical_and, logical_or, logical_not
41 # Search the optional module 'filters'
42 has_filters = True
43 try:
44 import scipy.ndimage.filters as filters
45 if debug: print 'Found scipy.ndimage.filters'
46 except ImportError:
47 try:
48 import numarray.nd_image.filters as filters
49 if debug: print 'Found numarray.nd_image.filters'
50 except ImportError:
51 if debug: print 'WARNING: No filters module found, filters disabled.'
52 has_filters = False
54 # Search the optional module 'measurements'
55 has_measurements = True
56 try:
57 import scipy.ndimage.measurements as M
58 print 'Found scipy.ndimage'
59 except ImportError:
60 try:
61 import numarray.nd_image.measurements as M
62 print 'Found numarray.nd_image'
63 except ImportError:
64 print 'WARNING: No ndimage module found, some features disabled.'
65 has_measurements = False
67 # Import matplotlib widgets and functions
68 from matplotlib.cm import get_cmap
69 from matplotlib.collections import LineCollection
70 from matplotlib.colors import colorConverter
72 # Some custom utility modules
73 from pyplotsuite.common.arrayfromfile import arrayfromfile, arrayFromZemax
74 from pyplotsuite.common.img import image2array
75 from pyplotsuite.common.geometry import circle
76 from pyplotsuite.common.version import read_version
77 from imageanalyzer.circularlist import CircularList
78 from imageanalyzer.section import ArraySection
79 from imageanalyzer.loadfile import *
80 from imageanalyzer.resampling import digital_binning
81 from imageanalyzer.integral import peak_integral1, peak_integral2t
83 # Import the profiler
84 #import profile
85 #profile.Profile.bias = 5e-6
87 def dprint(*args):
88 global debug
89 if debug: print args
92 # Implementation Classes
94 class ImageApp(GenericMainPlotWindow):
95 """
96 Main application window object.
97 """
98 def __init__(self, filename=None):
99 GenericMainPlotWindow.__init__(self, 'ImageWindow', gladefile,
100 autoconnect=True, makeAxis=False, makeToolbar=False)
102 self.image_loaded = False # Must stay before gui_setup()
103 self.gui_setup()
104 self.filename = filename
106 # Create additional attributes with default values
107 self.gridon = True
108 self.origin = 'upper' # Image origin ('upper' or 'lower')
109 self.gridcolor = 'white'
110 self.xPixelDim = 1
111 self.yPixelDim = 1
112 self.min = 0
113 self.max = 2**14+1
114 self.interp = 'Nearest' # corresponds to .set_active(12)
115 self.colormap = get_cmap('jet') # corresponds to .set_active(8)
116 #self.position = None
117 self.histogramApp = None
118 self.sectionApp = None
119 self.colors = ('green', 'red', 'blue', 'magenta', 'cyan', 'yellow',
120 'orange', 'gray')
122 self.segments = []
123 self.linescolors = []
124 self.measuring = False # - indicate if the measure button is pushed
125 self.secondPoint = False # - indicate if we're waiting for the second
126 # point of a distance measure
127 self.title = None
128 self.xlabel = None
129 self.ylabel = None
130 self.colorlist = None
131 self.sectionsLC = None # LinesCollection representing the sections
132 self.aspect = 1
133 self.PeakIntegralDialog = None
135 # Show the window and each child
136 self.window.show_all()
138 # Load the eventual image
139 if not filename == None:
140 self.load_image(filename)
141 else:
142 self.statusBar.push(self.context,
143 'Use menu File -> Open to load an image.')
145 def gui_setup(self):
146 # Create the matplotlib toolbar
147 self.mpl_toolbar = MyNavigationToolbar(self.canvas, self.window,
148 ButtonCallBack = self.on_measureButton_toggled,
149 icon_fname = sourcedir+'icons/measure-icon-20.png',
150 label = 'Measure Tool',
151 tooltip = 'Enable/Disable the "Measure Tool".'
153 toolbar_container = self.widgetTree.get_widget('MyToolbarAlignment')
154 toolbar_container.add(self.mpl_toolbar)
156 # Create an handle for the grid check box
157 self.gridCheckBox = self.widgetTree.get_widget('grid')
159 # Create handles for the SpinBoxes
160 self.minSpinButton = self.widgetTree.get_widget('minSpinButton')
161 self.maxSpinButton = self.widgetTree.get_widget('maxSpinButton')
163 # Set the handler for the colormap ComboBox
164 self.cmapComboBox = self.widgetTree.get_widget('cmapComboBox')
165 self.cmapComboBox.set_active(8) # 'jet'
167 # Set the handler for the interpolation ComboBox
168 self.interpComboBox = self.widgetTree.get_widget('interpComboBox')
169 self.interpComboBox.set_active(12) # 'Nearest'
171 self.openfileDialog = None
172 self.savefileDialog = None
175 # The following are plotting methods
177 def _load_file(self, fname):
179 Low-level function used by load_image to handle file open, format
180 autodetection and loading of basic data.
182 load_image requires that _load_file set the following:
183 - self.imagemode
184 - self.image_array
185 and optionally also self.image (the PIL image object).
187 try:
188 self.image_array, self.imagemode, self.image = load_file(fname)
189 except IOError, error_string:
190 self.statusBar.push(self.context, error_string)
191 return False
192 except UnknownFileType:
193 self.statusBar.push(self.context,
194 'File "%s" is of unkown type.' % fname)
195 return False
196 else:
197 return True
199 def load_image(self, filename):
201 Load the given image filename, and plot it. It set also the statical
202 attributes for a given image (.min, .max, .bits, .isColorImage)
205 # Try to open the file and load the image ...
206 if not self._load_file(filename): return
208 # ... and if the image il loaded ...
209 self.window.set_title(filename)
210 self.figure.clf()
211 self.axim = self.figure.add_subplot(111)
213 if self.imagemode == 'L':
214 self.nbit = 8
215 self.isColorImage = False
216 elif self.imagemode == 'I;16':
217 self.nbit = 14
218 self.isColorImage = False
219 elif self.imagemode == 'floatarray':
220 self.nbit = 8
221 self.isColorImage = False
222 else:
223 self.nbit = 8
224 self.isColorImage = True
226 if self.isColorImage:
227 self.statusBar.push(self.context,
228 'Image RGB(A): ignoring the range (min and max).')
229 else:
230 self.min = self.image_array.min()
231 self.max = self.image_array.max()
232 if self.imagemode == 'floatarray':
233 s = 'float values.'
234 else:
235 s = str(self.nbit)+' bit.'
236 self.statusBar.push(self.context,
237 'Image L (Luminance) with '+s)
239 self.autoset_extent()
240 self.plot_image(self.min, self.max)
241 self.image_loaded = True
243 def plot_image(self, min=None, max=None, interp=None, cmap_name=None,
244 origin=None, extent=None):
246 Plot the image self.image_array. The optional parameters allow to
247 choose min and max range value, interpolation method and colormap.
248 If not specified the default values are took from the object
249 attributes (self.min, self.max, self.cmap, self.interp).
251 Assumes the statical attributes for the given image to be set (for
252 example .isColorImage).
254 if not min == None: self.set_min(min)
255 if not max == None: self.set_max(max)
256 if not interp == None: self.interp = interp
257 if not origin == None: self.origin = origin
258 if not cmap_name == None: self.colormap = get_cmap(cmap_name)
259 if not extent == None: self.extent = extend
261 self.axim.clear()
263 self.i = self.axim.imshow(self.image_array,
264 interpolation=self.interp,
265 vmin=self.min, vmax=self.max,
266 cmap=self.colormap,
267 origin=self.origin,
268 extent=self.extent,
269 aspect=self.aspect)
271 if not self.isColorImage:
272 # Plot the colorbar
273 try:
274 # Try to plot the colorbar on an existing second axis
275 self.figure.colorbar(self.i, self.figure.axes[1])
276 except:
277 # Otherwise let colorbar() to create its default axis
278 self.figure.colorbar(self.i)
280 if self.gridon:
281 self.axim.grid(color=self.gridcolor)
282 # self.axim.axis('image')
283 self.set_title_and_labels()
284 if self.sectionsLC != None:
285 self.axim.add_collection(self.sectionsLC)
286 self.canvas.draw()
288 def autoset_extent(self):
290 Auto-set the image extent using image dimension and pixel size.
292 if self.isColorImage:
293 ly, lx, ch = self.image_array.shape
294 else:
295 ly, lx = self.image_array.shape
296 self.extent = (0, lx*self.xPixelDim, 0, ly*self.yPixelDim)
298 def set_min(self, min):
299 self.min = min
300 self.minSpinButton.set_value(min)
302 def set_max(self, max):
303 self.max = max
304 self.maxSpinButton.set_value(max)
306 def set_title_and_labels(self):
307 if self.title != None:
308 self.axim.set_title(self.title)
309 if self.xlabel != None:
310 self.axim.set_xlabel(self.xlabel)
311 if self.ylabel != None:
312 self.axim.set_ylabel(self.ylabel)
315 # Methods for the "Measure" tool and to manage sections
317 def on_canvas_clicked(self, event):
318 if not event.inaxes == self.axim: return
319 if not self.secondPoint:
320 self.secondPoint = True
321 print "x:", event.xdata, ", y:", event.ydata
322 self.x1 = int(event.xdata) + 0.5
323 self.y1 = int(event.ydata) + 0.5
324 m = 'First point: X1 = '+str(self.x1)+', Y1 = '+str(self.y1)+'.'
325 m += ' Select the second point.'
326 self.statusBar.push(self.context, m)
327 else:
328 print "x:", event.xdata, ", y:", event.ydata
329 x2 = int(event.xdata) + 0.5
330 y2 = int(event.ydata) + 0.5
332 self.segments.append(((self.x1, self.y1), (x2, y2)))
333 self.linescolors.append(self.colorlist.next())
334 self.sectionsLC = LineCollection(self.segments,
335 colors=self.linescolors)
336 self.sectionsLC.set_linewidth(3)
337 self.axim.add_collection(self.sectionsLC)
338 self.canvas.draw()
340 # Print distance to status bar
341 self.calculateDistance(self.segments[-1])
343 if self.sectionApp != None:
344 self.sectionApp.addsection(self.calculateSection(-1))
346 self.secondPoint = False
347 self.x1, self.y1 = None, None
349 def calculateDistance(self, section, useStatusBar=True):
350 (x1, y1), (x2, y2) = section
351 dx = (x2 - x1)
352 dy = (y2 - y1)
353 dist = sqrt(dx**2 + dy**2)
354 if useStatusBar:
355 m = 'X1 = %d, Y1 = %d; ' % (self.x1, self.y1)
356 m += 'X2 = %d, Y2 = %d; ' % (x2, y2)
357 m += 'Distance: %5.1f μm.' % (dist)
358 self.statusBar.push(self.context, m)
359 return dist
361 def calculateSection(self, segmentindex):
362 (x1, y1), (x2, y2) = self.segments[segmentindex]
364 print 'xpdim:', self.xPixelDim, 'ypdim:', self.yPixelDim
365 px = lambda xi: int(round(xi/float(self.xPixelDim)))
366 py = lambda yi: int(round(yi/float(self.yPixelDim)))
368 px1, px2, py1, py2 = px(x1), px(x2), py(y1), py(y2)
369 print 'Segment:', px1, px2, py1, py2
371 if self.origin == 'upper':
372 py1, py2 = self.image_array.shape[0] - array((py1, py2)) - 1
373 print 'Segment UpDown:', px1, px2, py1, py2
375 section = ArraySection(self.image_array, px1, px2, py1, py2,
376 self.xPixelDim, self.yPixelDim, debug=True)
378 # Recall the corresponding section color
379 section.color = self.linescolors[segmentindex]
381 return section
384 # The following are Toolbar callbacks
386 def on_cmapComboBox_changed(self, widget, *args):
387 colormap_name = self.cmapComboBox.get_active_text().lower()
388 self.colormap = get_cmap(colormap_name)
389 if not self.image_loaded: return
390 elif self.isColorImage:
391 self.colormap = get_cmap(colormap_name)
392 self.statusBar.push(self.context,
393 'Colormaps are ignored for color images.')
394 else:
395 self.i.set_cmap(self.colormap)
396 self.canvas.draw()
398 def on_interpComboBox_changed(self, widget, *args):
399 if not self.image_loaded: return None
400 interpolation_mode = self.interpComboBox.get_active_text()
401 dprint ("Interpolation:", interpolation_mode)
402 self.interp = interpolation_mode
403 self.i.set_interpolation(interpolation_mode)
404 self.canvas.draw()
406 def on_histogramButton_clicked(self, widget, *args):
407 if not self.image_loaded:
408 self.statusBar.push(self.context,
409 'You should open an image to visualyze the histogram.')
410 return None
411 elif self.isColorImage:
412 self.statusBar.push(self.context,
413 'Histogram view is not available for color images.')
414 return None
416 if self.histogramApp == None:
417 dprint ("new histogram")
418 self.histogramApp = HistogramApp(gladefile, self, self.nbit,
419 debug=debug)
420 else:
421 dprint ("old histogram")
422 self.histogramApp.window.show()
424 def on_applyButton_clicked(self, widget, *args):
425 min = self.minSpinButton.get_value()
426 max = self.maxSpinButton.get_value()
427 if min >= max:
428 self.minSpinButton.set_value(self.min)
429 self.maxSpinButton.set_value(self.max)
430 self.writeStatusBar("Invalid range value: must be Min < Max.")
431 elif self.min != min or self.max != max:
432 self.min, self.max = min, max
433 self.plot_image(self.min, self.max)
434 self.writeStatusBar("New range applied.")
436 def on_measureButton_toggled(self, widget, data=None):
437 self.measuring = not self.measuring
438 if self.measuring:
439 # Create the list of colors the first time
440 if self.colorlist == None:
441 self.colorlist = CircularList(len(self.colors))
442 for c in self.colors:
443 self.colorlist.append(colorConverter.to_rgba(c))
444 # Connect the cb to handle the measure
445 self.cid = self.canvas.mpl_connect('button_release_event',
446 self.on_canvas_clicked)
447 self.statusBar.push(self.context,
448 '"Measure Length" tool enabled: select the first point.')
449 else:
450 self.statusBar.push(self.context,
451 '"Measure Length" tool disabled.')
452 self.canvas.mpl_disconnect(self.cid)
455 # The following are menu callbacks
457 def on_openfile_activate(self, widget, *args):
458 if self.openfileDialog is None:
459 self.openfileDialog = OpenFileDialog(self)
460 else:
461 self.openfileDialog.window.show()
463 def on_savefile_activate(self, widget, *args):
464 if self.savefileDialog is None:
465 self.savefileDialog = SaveFileDialog(self)
466 else:
467 self.savefileDialog.window.show()
469 def on_close_activate(self, widget, *args):
470 self.figure.clf()
471 self.canvas.draw()
472 self.image_loaded = False
474 def on_quit_activate(self, widget, *args):
475 self.on_imagewindow_destroy(widget, *args)
477 def on_downup_activate(self, widget, *args):
478 if self.origin == 'upper': self.origin = 'lower'
479 else: self.origin = 'upper'
480 if self.image_loaded: self.plot_image()
481 if self.sectionApp != None:
482 print " *** WARNING: swap of segments not implemented."
484 def on_grid_activate(self, widget, data=None):
485 if self.gridon: self.axim.grid(False) # Toggle ON -> OFF
486 else: self.axim.grid(color=self.gridcolor) # Toggle OFF -> ON
487 self.canvas.draw()
488 self.gridon = not self.gridon
490 def on_whitegrid_activate(self, widget, *args):
491 if self.gridcolor == 'white': self.gridcolor = 'black'
492 else: self.gridcolor = 'white'
493 dprint ("grid color:", self.gridcolor)
495 if self.gridon:
496 self.axim.grid(self.gridon, color=self.gridcolor)
497 self.canvas.draw()
499 def on_information_activate(self, widget, *args):
500 about = AboutDialog()
502 def on_pixelDimension_activate(self, widget, *args):
503 PixelDimensionWindow(self)
505 def on_binning_activate(self, widget, *args):
506 BinningDialog(self)
508 def on_title_and_axes_labels_activate(self, widget, *args):
509 TitleAndAxesWindow(self)
511 def on_sections_activate(self, widget, *args):
512 if self.isColorImage:
513 self.statusBar.push(self.context,
514 'Function not available for color images.')
515 elif self.segments == []:
516 self.statusBar.push(self.context, 'No section to plot.')
517 elif self.sectionApp == None:
518 for index, color in enumerate(self.linescolors):
519 dprint (index, color, "cycle")
520 aSection = self.calculateSection(index)
521 print 'color', aSection.color
522 if self.sectionApp == None:
523 self.sectionApp = SectionApp(gladefile, aSection, self)
524 else:
525 self.sectionApp.addsection(aSection)
526 else:
527 self.sectionApp.window.show()
529 def on_sectionlist_activate(self, widget, *args):
530 colors = CircularList(len(self.colors))
531 colors.fromlist(self.colors)
532 text = ''
533 for n, seg in enumerate(self.segments):
534 (x1, y1), (x2, y2) = seg
535 lenAU = self.calculateDistance(seg, False)
536 text += '== Section '+ str(n+1) +' ('+ colors.get(n)+') ==\n'
537 text += 'x1 = %4d y1 = %4d\n' % (x1, y1)
538 text += 'x2 = %4d y2 = %4d\n' % (x2, y2)
539 text += 'Length: %5.2f, with pixel dimention (%2.2f x %2.2f).\n'\
540 % (lenAU, self.xPixelDim, self.yPixelDim)
541 text += '\n'
542 SectionListApp(text)
544 def on_clearsegments_activate(self, widget, *args):
545 if self.segments == []: return
546 if self.sectionApp != None:
547 self.sectionApp.destroy()
548 self.sectionApp = None
549 self.segments = []
550 self.linescolors = []
551 self.colorlist.resetIndex()
552 self.sectionsLC = None
553 #self.axim.clear()
554 self.plot_image()
556 def on_resetrange_activate(self, widget, *args):
557 self.plot_image(
558 min=int(self.image_array.min()),
559 max=int(self.image_array.max()))
561 def on_gaussianFilter_activate(self, widget, *args):
562 dprint ("Gaussian Filer")
563 GaussianFilterWindow(self)
565 def on_scrollingmean_activate(self, widget, *args):
566 if not self.isColorImage:
567 print 'Type:', self.image_array.dtype.name
568 self.image_array = self.image_array.astype('float32')
569 print 'Convolution spatial filter ... '
570 kernel = array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype='float32')
571 kernel /= kernel.sum()
572 print ' - Convolution kernel: \n', kernel
573 self.image_array = filters.convolve(self.image_array, kernel)
574 self.imagemode = 'floatarray'
575 self.plot_image()
576 print 'OK\n'
577 else:
578 self.statusBar.push(self.context,
579 "This mean can't be performed on color images.")
581 def on_reload_activate(self, widget, *args):
582 dprint ("Reloading image ...")
583 if self.filename != None:
584 self.on_clearsegments_activate(None, None)
585 self.load_image(self.filename)
587 def on_peakintegral_activate(self, widget, *args):
588 if self.PeakIntegralDialog is None:
589 self.PeakIntegralDialog = PeakIntegralDialog(self)
590 else:
591 self.PeakIntegralDialog.window.show()
593 def on_all_activate(self, widget, *args):
594 ImagePropertiesDialog(self)
597 class SectionListApp(GenericSecondaryWindow):
599 This class implement the text window used to show the sections' list.
601 def __init__(self, text):
602 GenericSecondaryWindow.__init__(self, 'SectionListWindow', gladefile)
604 textview = self.widgetTree.get_widget('sectionsTextView')
605 buffer = textview.get_buffer()
606 buffer.set_text(text)
608 def on_closeButton_clicked(self, widget, data=None):
609 self.window.destroy()
612 class GaussianFilterWindow(DialogWindowWithCancel):
614 Dialog for setting the gaussian filter options.
616 def __init__(self, callerApp):
617 DialogWindowWithCancel.__init__(self, 'GaussianFilterWindow',
618 gladefile, callerApp)
619 self.sigmaSpinButton = self.widgetTree.get_widget('sigmaSpinButton')
621 def on_okButton_clicked(self, widget, *args):
622 sigma = self.sigmaSpinButton.get_value()
623 dprint ("Ok, sigma =", sigma)
624 self.callerApp.image_array = filters.gaussian_filter(
625 self.callerApp.image_array, sigma)
626 self.callerApp.imagemode = 'floatarray'
627 self.callerApp.plot_image()
628 self.window.destroy()
631 class BinningDialog(DialogWindowWithCancel):
633 Dialog for setting the binning parameters.
635 def __init__(self, callerApp):
636 DialogWindowWithCancel.__init__(self, 'BinningDialog',
637 gladefile, callerApp)
639 self.xBinSpinButt = self.widgetTree.get_widget('xBinSpinButt')
640 self.yBinSpinButt = self.widgetTree.get_widget('yBinSpinButt')
642 def on_okButton_clicked(self, widget, *args):
643 dprint ("OK")
644 xb = int(self.xBinSpinButt.get_value())
645 yb = int(self.yBinSpinButt.get_value())
646 a = self.callerApp.image_array
647 self.callerApp.image_array = digital_binning(a, xb, yb)
648 self.callerApp.autoset_extent()
649 a = self.callerApp.image_array
650 self.callerApp.plot_image(min=int(a.min()), max=int(a.max()))
651 self.window.destroy()
654 class PixelDimensionWindow(DialogWindowWithCancel):
656 Dialog for setting the pixel dimension.
658 def __init__(self, callerApp):
659 DialogWindowWithCancel.__init__(self, 'PixelDimensionWindow',
660 gladefile, callerApp)
662 xDim = self.callerApp.xPixelDim
663 yDim = self.callerApp.yPixelDim
665 self.xPixDimSpinButt = self.widgetTree.get_widget('xPixDimSpinButt')
666 self.yPixDimSpinButt = self.widgetTree.get_widget('yPixDimSpinButt')
667 self.aspectSpinButt = self.widgetTree.get_widget('aspectSpinButt')
668 self.xPixDimSpinButt.set_value(xDim)
669 self.yPixDimSpinButt.set_value(yDim)
670 self.aspectSpinButt.set_value(self.callerApp.aspect)
672 def on_okButton_clicked(self, widget, *args):
673 dprint ("OK")
674 self.callerApp.xPixelDim = self.xPixDimSpinButt.get_value()
675 self.callerApp.yPixelDim = self.yPixDimSpinButt.get_value()
676 self.callerApp.aspect = self.aspectSpinButt.get_value()
677 self.callerApp.autoset_extent()
678 self.callerApp.plot_image()
679 self.window.destroy()
681 class PeakIntegralDialog(DialogWindowWithCancel):
683 Dialog for calculating the integral of a peak.
685 def __init__(self, callerApp):
686 DialogWindowWithCancel.__init__(self, 'PeakIntegralDialog',
687 gladefile, callerApp)
689 self.contour = None
690 self.circle = None
691 self.allowReplot = False
692 self.yc, self.xc = None, None
693 self.t = self.rt = None
694 self.OutputDialog = None
696 self.image = self.callerApp.image_array
697 self.ax = self.callerApp.axim
698 self.canvas = self.callerApp.canvas
699 self.origin = self.callerApp.origin
701 self.ax.figure.canvas.mpl_connect('button_release_event',
702 self.get_cursor_cb)
703 self.callerApp.writeStatusBar(
704 "Click on the image to select the center of integration.")
706 self.thresholdSpinButt = self.widgetTree.get_widget(
707 'thresholdSpinButt')
708 self.rThresholdSpinButt = self.widgetTree.get_widget(
709 'rThresholdSpinButt')
710 self.maxRadiousSpinButt = self.widgetTree.get_widget(
711 'maxRadiousSpinButt')
712 self.xCircleSpinButt = self.widgetTree.get_widget('xCircleSpinButt')
713 self.yCircleSpinButt = self.widgetTree.get_widget('yCircleSpinButt')
715 t = (self.image.max() - self.image.min())*0.1 + self.image.min()
716 self.thresholdSpinButt.set_value(t)
718 r = self.image.shape[0] / 8
719 self.maxRadiousSpinButt.set_value(r)
721 self.yc, self.xc = M.maximum_position(self.image)
722 if self.origin == 'upper':
723 dprint('upper origin')
724 self.yc = self.image.shape[0] - self.yc
726 self.xCircleSpinButt.set_value(self.xc)
727 self.yCircleSpinButt.set_value(self.yc)
728 self.allowReplot = True
729 self.update_all()
731 def calculate_integral(self):
732 dprint('Calculating integral')
733 a, t, rt, xc, yc, r = self.image, self.t, self.rt, self.xc, self.yc, self.r
734 if self.origin == 'upper':
735 yc = self.image.shape[0] - yc
737 raw_integral, domain_size, raw_residual, offset_integral, offset = \
738 peak_integral2t(a, t, rt, xc, yc, r)
740 data = "Raw Integral: \t %d \n" % (raw_integral)
741 data += "Integrated pixels: \t %d \n" % (domain_size)
742 data += "Raw Residual: \t %d \n" % (raw_residual)
743 data += "Residual pixels: \t %d \n" % (a.size - domain_size)
744 data += "Integral - offset: \t %1.2f \n" % (offset_integral)
745 data += "Offset: \t\t %1.4f \n" % (offset)
746 data += "t, rt, xc, yc, r: \t %d, %d, %d, %d, %r\n\n" % (t, rt, xc, yc, r)
748 if self.OutputDialog is None:
749 self.OutputDialog = OutputDialog(data)
750 else:
751 self.OutputDialog.add_text(data)
752 self.OutputDialog.window.show()
754 # Plotting methods
756 def get_cursor_cb(self, event):
757 if event.inaxes:
758 dprint('setting new center')
759 self.allowReplot = False
760 self.xCircleSpinButt.set_value(event.xdata)
761 self.allowReplot = True
762 self.yCircleSpinButt.set_value(event.ydata)
763 self.canvas.draw()
765 def update_all(self):
766 self.update_circle()
767 self.update_mask()
768 self.update_contour()
769 self.canvas.draw()
771 def update_mask(self):
772 self.t = self.thresholdSpinButt.get_value()
773 self.rt = self.rThresholdSpinButt.get_value()
775 if self.t > 0:
776 mask = self.image > self.t
777 else:
778 mask = True
780 if self.t > 0:
781 rmask = self.image > self.rt
782 else:
783 rmask = True
785 # Creating distance function using numpy broadcasting rules
786 x = arange(self.image.shape[1])
787 y = arange(self.image.shape[0]).reshape(self.image.shape[0],1)
788 if self.origin == 'upper':
789 yc = self.image.shape[0] - self.yc
790 else:
791 yc = self.yc
792 distance = sqrt((x-self.xc)**2 + (y-yc)**2)
794 self.mask = logical_or(logical_and(mask, distance < self.r),
795 logical_and(rmask, distance >= self.r))
797 #self.yc, self.xc = M.center_of_mass(self.mask)
798 #self.changed = True
800 def update_contour(self):
801 if self.mask is not True:
802 if self.contour is not None:
803 self.callerApp.plot_image()
804 self.circle = None
805 self.update_circle()
807 self.contour = self.ax.contourf(self.mask, [-1, 0.5],
808 colors=['black'], alpha=0.7, origin=self.origin)
809 #self.canvas.draw()
811 def update_circle(self):
812 self.r = self.maxRadiousSpinButt.get_value()
813 self.xc = self.xCircleSpinButt.get_value()
814 self.yc = self.yCircleSpinButt.get_value()
816 #if self.origin == 'upper' and self.changed:
817 # self.yc = 512 - self.yc
818 # self.changed = False
820 if self.allowReplot:
821 x, y = circle(self.xc, self.yc, self.r)
823 if self.circle is None:
824 self.circle, = self.ax.plot(x, y,
825 lw=2, color='white', linestyle='--')
826 else:
827 self.circle.set_data(x,y)
829 self.ax.set_xlim(0,self.image.shape[1])
830 self.ax.set_ylim(0,self.image.shape[0])
831 self.canvas.draw()
834 # GUI callbacks
836 def on_thresholdSpinButt_value_changed(self, widget, *args):
837 dprint ("threshold value changed")
838 if self.allowReplot:
839 self.update_mask()
840 self.update_contour()
841 self.canvas.draw()
843 def on_rThresholdSpinButt_value_changed(self, widget, *args):
844 dprint ("residual threshold value changed")
845 if self.allowReplot:
846 self.update_mask()
847 self.update_contour()
848 self.canvas.draw()
851 def on_maxRadiousSpinButt_value_changed(self, widget, *args):
852 dprint ("radious value changed")
853 if self.allowReplot:
854 self.update_circle()
855 self.canvas.draw()
857 def on_xCircleSpinButt_value_changed(self, widget, *args):
858 dprint ("x coord value changed")
859 if self.allowReplot:
860 self.update_circle()
861 self.canvas.draw()
863 def on_yCircleSpinButt_value_changed(self, widget, *args):
864 dprint ("y coord value changed")
865 if self.allowReplot:
866 self.update_circle()
867 self.canvas.draw()
869 def on_calculateButton_clicked(self, widget, *args):
870 dprint ("Calculate")
871 self.calculate_integral()
873 def on_okButton_clicked(self, widget, *args):
874 dprint ("OK")
875 self.calculate_integral()
876 self.callerApp.plot_image()
877 self.window.destroy()
879 def on_destroy(self, widget, *args):
880 dprint ("destroing dialog")
881 self.callerApp.plot_image()
882 self.callerApp.PeakIntegralDialog = None
883 self.window.destroy()
885 class ImagePropertiesDialog(ChildWindow):
887 Dialog to calculate image properties.
889 def __init__(self, callerApp):
890 ChildWindow.__init__(self, 'ImagePropertiesDialog', gladefile,
891 callerApp)
893 self.widgetTree.get_widget('fnameEntry').set_text(
894 os.path.basename(self.callerApp.filename))
895 self.widgetTree.get_widget('pathEntry').set_text(
896 os.path.dirname(self.callerApp.filename))
897 self.widgetTree.get_widget('sizeEntry').set_text("%3.1f KB" %\
898 (os.stat(self.callerApp.filename).st_size/1024.,))
900 self.nPixelsEntry = self.widgetTree.get_widget('nPixelsEntry')
901 self.shapeLabel = self.widgetTree.get_widget('shapeLabel')
902 self.maxEntry = self.widgetTree.get_widget('maxEntry')
903 self.maxPosLabel = self.widgetTree.get_widget('maxPosLabel')
904 self.minEntry = self.widgetTree.get_widget('minEntry')
905 self.minPosLabel = self.widgetTree.get_widget('minPosLabel')
906 self.varEntry = self.widgetTree.get_widget('varEntry')
907 self.stdEntry = self.widgetTree.get_widget('stdEntry')
908 self.sumEntry = self.widgetTree.get_widget('sumEntry')
909 self.meanEntry = self.widgetTree.get_widget('meanEntry')
910 self.medianEntry = self.widgetTree.get_widget('medianEntry')
911 self.modeEntry = self.widgetTree.get_widget('modeEntry')
912 im = self.callerApp.image_array
913 self.nPixelsEntry.set_text(str(im.size))
914 self.shapeLabel.set_text("%d x %d" % im.shape[::-1])
915 self.maxEntry.set_text(str(im.max()))
916 self.maxPosLabel.set_text("@ %d x %d" % M.maximum_position(im)[::-1])
917 self.minEntry.set_text(str(im.min()))
918 self.minPosLabel.set_text("@ %d x %d" % M.minimum_position(im)[::-1])
919 self.varEntry.set_text("%8.3f" % im.var())
920 self.stdEntry.set_text("%8.3f" % im.std())
921 self.sumEntry.set_text("%d" % im.sum())
922 self.meanEntry.set_text("%8.3f" % im.mean())
923 self.medianEntry.set_text("%8.2f" %(im.min() + (im.max()-im.min())*0.5))
924 #self.modeEntry.set_text(str(im.mode()))
926 def on_closeButton_clicked(self, widget, *args):
927 self.window.destroy()
931 class TitleAndAxesWindow(DialogWindowWithCancel):
933 Dialog for setting Title and axes labels.
935 def __init__(self, callerApp):
936 DialogWindowWithCancel.__init__(self, 'TitleAndAxesWindow',
937 gladefile, callerApp)
939 self.titleEntry = self.widgetTree.get_widget('titleEntry')
940 self.xlabelEntry = self.widgetTree.get_widget('xlabelEntry')
941 self.ylabelEntry = self.widgetTree.get_widget('ylabelEntry')
943 def sync(field, entry):
944 if field != None: entry.set_text(field)
946 sync(self.callerApp.title, self.titleEntry)
947 sync(self.callerApp.xlabel, self.xlabelEntry)
948 sync(self.callerApp.ylabel, self.ylabelEntry)
950 def on_okButton_clicked(self, widget, *args):
951 dprint ("Ok clicked")
952 self.callerApp.axim.set_title(self.titleEntry.get_text())
953 self.callerApp.axim.set_xlabel(self.xlabelEntry.get_text())
954 self.callerApp.axim.set_ylabel(self.ylabelEntry.get_text())
955 self.callerApp.canvas.draw()
957 self.callerApp.title = self.titleEntry.get_text()
958 self.callerApp.xlabel = self.xlabelEntry.get_text()
959 self.callerApp.ylabel = self.ylabelEntry.get_text()
961 self.window.destroy()
963 class SaveFileDialog(GenericSaveFileDialog):
965 This class implements the "Save File" dialog for the main window.
967 def __init__(self, callerApp):
968 GenericSaveFileDialog.__init__(self, 'savefileDialog',
969 gladefile, callerApp)
971 def saveToSelectedFile(self):
972 filename = self.filename + '.pickle'
973 try:
974 f = open(filename, 'w')
975 cPickle.dump(self.callerApp.image_array, f)
976 f.close()
977 self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
978 except:
979 self.callerApp.writeStatusBar("Can't save file '%s'." % (filename))
980 self.window.hide()
982 class SaveOutputDialog(GenericSaveFileDialog):
984 This class implements a "Save File" dialog for the output of a dialog.
986 def __init__(self, data, callerApp=None):
987 GenericSaveFileDialog.__init__(self, 'savefileDialog',
988 gladefile, callerApp)
989 self.data = data
991 def saveToSelectedFile(self):
992 filename = self.filename
993 try:
994 f = open(filename, 'w')
995 f.write(self.data)
996 f.close()
997 #self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
998 except:
999 #self.callerApp.writeStatusBar("Can't save file '%s'." % (filename))
1000 pass
1001 self.window.hide()
1003 class OutputDialog(GenericSecondaryWindow):
1005 This class implements a dialog to output some text.
1007 def __init__(self, text):
1008 GenericSecondaryWindow.__init__(self, 'OutputDialog', gladefile)
1009 textview = self.widgetTree.get_widget('textview')
1010 self.buffer = textview.get_buffer()
1011 self.buffer.set_text(text)
1012 self.data = text
1014 def add_text(self, text):
1015 self.data += text
1016 self.buffer.set_text(self.data)
1018 def on_saveButton_clicked(self, widget, *args):
1019 dprint ("clear clicked")
1020 SaveOutputDialog(self.data)
1022 def on_clearButton_clicked(self, widget, *args):
1023 dprint ("save clicked")
1024 self.data=''
1025 self.buffer.set_text('')
1027 def on_closeButton_clicked(self, widget, *args):
1028 dprint ("close clicked")
1029 self.window.hide()
1031 def on_delete_event(self, widget, *args):
1032 dprint ("delete signal")
1033 self.window.hide()
1034 return True
1036 def on_destroy(self, widget, *args):
1037 dprint ("destroy signal")
1038 self.window.destroy()
1040 class OpenFileDialog(DialogWindowWithCancel):
1042 This class implements the "Open File" dialog.
1044 def __init__(self, callerApp):
1045 DialogWindowWithCancel.__init__(self, 'openfileDialog',
1046 gladefile, callerApp)
1048 def openSelectedFile(self):
1049 filename = self.window.get_filename()
1050 if filename != None:
1051 self.window.hide()
1052 self.callerApp.load_image(filename)
1053 self.window.hide()
1055 def on_openButton_clicked(self, widget, *args):
1056 dprint ("open button clicked", self.window.get_filename())
1057 self.openSelectedFile()
1059 def on_openfileDialog_file_activated(self, widget, *args):
1060 dprint ("Open File Dialog: file_activated", self.window.get_filename())
1062 def on_openfileDialog_response(self, widget, *args):
1063 dprint ("\nOpen File Dialog: response event")
1065 def on_cancelButton_clicked(self, widget, *args):
1066 # Don't destroy the dialog so we remeber the folder next time
1067 self.window.hide()
1068 return True
1070 class AboutDialog(GenericSecondaryWindow):
1072 Object for the "About Dialog".
1074 def __init__(self):
1075 GenericSecondaryWindow.__init__(self, 'aboutDialog', gladefile)
1076 self.window.set_version(read_version(rootdir))
1077 self.window.connect("response", lambda d, r: d.destroy())
1079 def main():
1080 file_name = sourcedir + 'samples/test-img.tif'
1081 files = sys.argv[1:]
1082 if len(files) < 1: files =(file_name,)
1084 for filename in files:
1085 try:
1086 file = open(filename,'r')
1087 ia = ImageApp(filename)
1088 file.close()
1089 except IOError:
1090 print "\n File '"+filename+"' is not existent or not readable.\n"
1091 ia = ImageApp()
1092 ia.start()
1094 if __name__ == "__main__": main()