IA: Minor README changes
[pyplotsuite.git] / pyplotsuite / imageanalyzer2.py
blob754e4c6de90bbd9da455c5ffe39127601c5ffa91
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 glob
19 import imp
20 rootdir = os.path.abspath(imp.find_module('pyplotsuite')[1]) + '/'
21 sourcedir = rootdir + 'imageanalyzer/'
22 gladefile = sourcedir + 'image_analyzer.glade'
24 dark_suffix = '-buio'
25 debug = True
27 try:
28 from PIL import Image
29 except ImportError:
30 print '\n You need to install the Python Imaging Library (PIL) module. \n'
31 sys.exit(3)
33 # Generic window description classes
34 from pyplotsuite.common.gtk_generic_windows import \
35 GenericMainPlotWindow, MyNavigationToolbar, GenericSecondaryWindow, \
36 DialogWindowWithCancel, GenericSaveFileDialog, ChildWindow
38 # Dialogs classes
39 from imageanalyzer.histogramdialog import HistogramApp
40 from imageanalyzer.sectionsdialog import SectionApp
42 # Mathematical functions
43 from numpy import arange, array, sqrt, log10, \
44 logical_and, logical_or, logical_not
46 # Search the optional module 'filters'
47 has_filters = True
48 try:
49 import scipy.ndimage.filters as filters
50 if debug: print 'Found scipy.ndimage.filters'
51 except ImportError:
52 try:
53 import numarray.nd_image.filters as filters
54 if debug: print 'Found numarray.nd_image.filters'
55 except ImportError:
56 if debug: print 'WARNING: No filters module found, filters disabled.'
57 has_filters = False
59 # Search the optional module 'measurements'
60 has_measurements = True
61 try:
62 import scipy.ndimage.measurements as M
63 print 'Found scipy.ndimage'
64 except ImportError:
65 try:
66 import numarray.nd_image.measurements as M
67 print 'Found numarray.nd_image'
68 except ImportError:
69 print 'WARNING: No ndimage module found, some features disabled.'
70 has_measurements = False
72 # Import matplotlib widgets and functions
73 from matplotlib.cm import get_cmap
74 from matplotlib.collections import LineCollection
75 from matplotlib.colors import colorConverter
77 # Some custom utility modules
78 from pyplotsuite.common.arrayfromfile import arrayfromfile, arrayFromZemax
79 from pyplotsuite.common.img import image2array
80 from pyplotsuite.common.geometry import circle
81 from pyplotsuite.common.version import read_version
82 from imageanalyzer.circularlist import CircularList
83 from imageanalyzer.section import ArraySection
84 from imageanalyzer.loadfile import *
85 from imageanalyzer.resampling import digital_binning
86 from imageanalyzer.integral import peak_integral1, peak_integral2t
88 # Import the profiler
89 #import profile
90 #profile.Profile.bias = 5e-6
92 def dprint(*args):
93 global debug
94 if debug: print args
97 # Main Window Class
99 class ImageApp(GenericMainPlotWindow):
101 Main application window object.
103 def __init__(self, filename=None):
104 GenericMainPlotWindow.__init__(self, 'ImageWindow', gladefile,
105 autoconnect=True, makeAxis=False, makeToolbar=False)
107 self.image_loaded = False # Must stay before gui_setup()
108 self.gui_setup()
109 self.filename = filename
111 # Create additional attributes with default values
112 self.gridon = True
113 self.origin = 'lower' # Image origin ('upper' or 'lower')
114 self.gridcolor = 'white'
115 self.xPixelDim = 1
116 self.yPixelDim = 1
117 self.min = 0
118 self.max = 2**14+1
119 self.interp = 'Nearest' # corresponds to .set_active(12)
120 self.colormap = get_cmap('jet') # corresponds to .set_active(8)
121 #self.position = None
122 self.histogramApp = None
123 self.sectionApp = None
124 self.colors = ('green', 'red', 'blue', 'magenta', 'cyan', 'yellow',
125 'orange', 'gray')
127 self.segments = []
128 self.linescolors = []
129 self.measuring = False # - indicate if the measure button is pushed
130 self.secondPoint = False # - indicate if we're waiting for the second
131 # point of a distance measure
132 self.title = None
133 self.xlabel = None
134 self.ylabel = None
135 self.colorlist = None
136 self.sectionsLC = None # LinesCollection representing the sections
137 self.aspect = 1
138 self.PeakIntegralDialog = None
140 # Show the window and each child
141 self.window.show_all()
143 self.load_files(filename)
145 def generate_file_list(self, current_path=None):
146 if current_path is None: current_path = '.'
147 print 'current_path: ', current_path
149 extensions = ['jpg', 'jpeg', 'png', 'tif', 'tiff']
150 file_list = []
151 for e in extensions:
152 for ee in [e.lower(), e.upper(), e.title()]:
153 file_list += glob.glob(current_path+'/*.'+ee)
155 print 'file_list: ', file_list
156 return file_list
159 def load_files(self, filename, current_path=None):
160 self.file_list = self.generate_file_list(current_path)
162 if len(self.file_list) == 0 and filename is None:
163 self.statusBar.push(self.context,
164 'Use menu File -> Open to load an image.')
165 return
167 self.file_index = 0
168 if filename in self.file_list:
169 self.file_index = self.file_list.index(filename)
170 elif filename is not None:
171 self.file_list = [filename] + self.file_list
173 self.load_image(self.file_list[self.file_index])
176 def gui_setup(self):
177 # Create the matplotlib toolbar
178 self.mpl_toolbar = MyNavigationToolbar(self.canvas, self.window,
179 ButtonCallBack = self.on_measureButton_toggled,
180 icon_fname = sourcedir+'icons/measure-icon-20.png',
181 label = 'Measure Tool',
182 tooltip = 'Enable/Disable the "Measure Tool".'
184 toolbar_container = self.widgetTree.get_widget('MyToolbarAlignment')
185 toolbar_container.add(self.mpl_toolbar)
187 # Create an handle for the grid check box
188 self.gridCheckBox = self.widgetTree.get_widget('grid')
190 # Create handles for the SpinBoxes
191 self.minSpinButton = self.widgetTree.get_widget('minSpinButton')
192 self.maxSpinButton = self.widgetTree.get_widget('maxSpinButton')
194 # Set the handler for the colormap ComboBox
195 self.cmapComboBox = self.widgetTree.get_widget('cmapComboBox')
196 self.cmapComboBox.set_active(8) # 'jet'
198 # Set the handler for the interpolation ComboBox
199 self.interpComboBox = self.widgetTree.get_widget('interpComboBox')
200 self.interpComboBox.set_active(12) # 'Nearest'
202 self.openfileDialog = None
203 self.savefileDialog = None
207 # The following are plotting methods
209 def _load_file(self, fname):
211 Low-level function used by load_image to handle file open, format
212 autodetection and loading of basic data.
214 load_image requires that _load_file set the following:
215 - self.imagemode
216 - self.image_array
217 and optionally also self.image (the PIL image object).
219 try:
220 self.image_array, self.imagemode, self.image = load_file(fname)
221 except IOError, error_string:
222 self.statusBar.push(self.context, error_string)
223 return False
224 except UnknownFileType:
225 self.statusBar.push(self.context,
226 'File "%s" is of unknown type.' % fname)
227 return False
228 else:
229 return True
231 def load_image(self, filename):
233 Load the given image filename, and plot it. It set also the statical
234 attributes for a given image (.min, .max, .bits, .isColorImage)
237 # Try to open the file and load the image ...
238 if not self._load_file(filename): return
240 # ... and if the image is loaded ...
241 self.window.set_title(os.path.basename(filename))
242 self.figure.clf()
243 self.axim = self.figure.add_subplot(111)
245 if self.imagemode == 'L':
246 self.nbit = 8
247 self.isColorImage = False
248 elif self.imagemode == 'I;16':
249 self.nbit = 14
250 self.isColorImage = False
251 elif self.imagemode == 'floatarray':
252 self.nbit = 8
253 self.isColorImage = False
254 else:
255 self.nbit = 8
256 self.isColorImage = True
258 if self.isColorImage:
259 self.statusBar.push(self.context,
260 'Image RGB(A): ignoring the range (min and max).')
261 else:
262 self.min = self.image_array.min()
263 self.max = self.image_array.max()
264 if self.imagemode == 'floatarray':
265 s = 'float values.'
266 else:
267 s = str(self.nbit)+' bit.'
268 self.statusBar.push(self.context,
269 'Image L (Luminance) with '+s)
271 self.autoset_extent()
272 self.plot_image(self.min, self.max)
273 self.image_loaded = True
275 def plot_image(self, min=None, max=None, interp=None, cmap_name=None,
276 origin=None, extent=None):
278 Plot the image self.image_array. The optional parameters allow to
279 choose min and max range value, interpolation method and colormap.
280 If not specified the default values are took from the object
281 attributes (self.min, self.max, self.cmap, self.interp).
283 Assumes the statical attributes for the given image to be set (for
284 example .isColorImage).
286 if not min == None: self.set_min(min)
287 if not max == None: self.set_max(max)
288 if not interp == None: self.interp = interp
289 if not origin == None: self.origin = origin
290 if not cmap_name == None: self.colormap = get_cmap(cmap_name)
291 if not extent == None: self.extent = extend
293 self.axim.clear()
295 self.i = self.axim.imshow(self.image_array,
296 interpolation=self.interp,
297 vmin=self.min, vmax=self.max,
298 cmap=self.colormap,
299 origin=self.origin,
300 extent=self.extent,
301 aspect=self.aspect)
303 if not self.isColorImage:
304 # Plot the colorbar
305 try:
306 # Try to plot the colorbar on an existing second axis
307 self.figure.colorbar(self.i, self.figure.axes[1])
308 except:
309 # Otherwise let colorbar() to create its default axis
310 self.figure.colorbar(self.i)
312 if self.gridon:
313 self.axim.grid(color=self.gridcolor)
314 # self.axim.axis('image')
315 self.set_title_and_labels()
316 if self.sectionsLC != None:
317 self.axim.add_collection(self.sectionsLC)
318 self.canvas.draw()
320 def autoset_extent(self):
322 Auto-set the image extent using image dimension and pixel size.
324 if self.isColorImage:
325 ly, lx, ch = self.image_array.shape
326 else:
327 ly, lx = self.image_array.shape
328 self.extent = (0, lx*self.xPixelDim, 0, ly*self.yPixelDim)
330 def set_min(self, min):
331 self.min = min
332 self.minSpinButton.set_value(min)
334 def set_max(self, max):
335 self.max = max
336 self.maxSpinButton.set_value(max)
338 def set_title_and_labels(self):
339 if self.title != None:
340 self.axim.set_title(self.title)
341 if self.xlabel != None:
342 self.axim.set_xlabel(self.xlabel)
343 if self.ylabel != None:
344 self.axim.set_ylabel(self.ylabel)
347 # Methods for the "Measure" tool and to manage sections
349 def on_canvas_clicked(self, event):
350 if not event.inaxes == self.axim: return
351 if not self.secondPoint:
352 self.secondPoint = True
353 print "x:", event.xdata, ", y:", event.ydata
354 self.x1 = int(event.xdata) + 0.5
355 self.y1 = int(event.ydata) + 0.5
356 m = 'First point: X1 = '+str(self.x1)+', Y1 = '+str(self.y1)+'.'
357 m += ' Select the second point.'
358 self.statusBar.push(self.context, m)
359 else:
360 print "x:", event.xdata, ", y:", event.ydata
361 x2 = int(event.xdata) + 0.5
362 y2 = int(event.ydata) + 0.5
364 self.segments.append(((self.x1, self.y1), (x2, y2)))
365 self.linescolors.append(self.colorlist.next())
366 self.sectionsLC = LineCollection(self.segments,
367 colors=self.linescolors)
368 self.sectionsLC.set_linewidth(3)
369 self.axim.add_collection(self.sectionsLC)
370 self.canvas.draw()
372 # Print distance to status bar
373 self.calculateDistance(self.segments[-1])
375 if self.sectionApp != None:
376 self.sectionApp.addsection(self.calculateSection(-1))
378 self.secondPoint = False
379 self.x1, self.y1 = None, None
381 def calculateDistance(self, section, useStatusBar=True):
382 (x1, y1), (x2, y2) = section
383 dx = (x2 - x1)
384 dy = (y2 - y1)
385 dist = sqrt(dx**2 + dy**2)
386 if useStatusBar:
387 m = 'X1 = %d, Y1 = %d; ' % (self.x1, self.y1)
388 m += 'X2 = %d, Y2 = %d; ' % (x2, y2)
389 m += 'Distance: %5.1f μm.' % (dist)
390 self.statusBar.push(self.context, m)
391 return dist
393 def calculateSection(self, segmentindex):
394 (x1, y1), (x2, y2) = self.segments[segmentindex]
396 print 'xpdim:', self.xPixelDim, 'ypdim:', self.yPixelDim
397 px = lambda xi: int(round(xi/float(self.xPixelDim)))
398 py = lambda yi: int(round(yi/float(self.yPixelDim)))
400 px1, px2, py1, py2 = px(x1), px(x2), py(y1), py(y2)
401 print 'Segment:', px1, px2, py1, py2
403 if self.origin == 'upper':
404 py1, py2 = self.image_array.shape[0] - array((py1, py2)) - 1
405 print 'Segment UpDown:', px1, px2, py1, py2
407 section = ArraySection(self.image_array, px1, px2, py1, py2,
408 self.xPixelDim, self.yPixelDim, debug=True)
410 # Recall the corresponding section color
411 section.color = self.linescolors[segmentindex]
413 return section
416 # The following are Toolbar callbacks
418 def on_cmapComboBox_changed(self, widget, *args):
419 colormap_name = self.cmapComboBox.get_active_text().lower()
420 self.colormap = get_cmap(colormap_name)
421 if not self.image_loaded: return
422 elif self.isColorImage:
423 self.colormap = get_cmap(colormap_name)
424 self.statusBar.push(self.context,
425 'Colormaps are ignored for color images.')
426 else:
427 self.i.set_cmap(self.colormap)
428 self.canvas.draw()
430 def on_interpComboBox_changed(self, widget, *args):
431 if not self.image_loaded: return None
432 interpolation_mode = self.interpComboBox.get_active_text()
433 dprint ("Interpolation:", interpolation_mode)
434 self.interp = interpolation_mode
435 self.i.set_interpolation(interpolation_mode)
436 self.canvas.draw()
438 def on_histogramButton_clicked(self, widget, *args):
439 if not self.image_loaded:
440 self.statusBar.push(self.context,
441 'You should open an image to visualyze the histogram.')
442 return None
443 elif self.isColorImage:
444 self.statusBar.push(self.context,
445 'Histogram view is not available for color images.')
446 return None
448 if self.histogramApp == None:
449 dprint ("new histogram")
450 self.histogramApp = HistogramApp(gladefile, self, self.nbit,
451 debug=debug)
452 else:
453 dprint ("old histogram")
454 self.histogramApp.window.show()
456 def on_maxSpinButton_value_changed(self, widget, *args):
457 max = self.maxSpinButton.get_value()
458 if self.min >= max:
459 self.maxSpinButton.set_value(self.max)
460 self.writeStatusBar("Invalid range value: must be Min < Max.")
461 elif self.max != max:
462 self.max = max
463 self.plot_image(self.min, self.max)
464 self.writeStatusBar("New range applied.")
466 def on_minSpinButton_value_changed(self, widget, *args):
467 min = self.minSpinButton.get_value()
468 if self.max <= min:
469 self.minSpinButton.set_value(self.min)
470 self.writeStatusBar("Invalid range value: must be Min < Max.")
471 elif self.min != min:
472 self.min = min
473 self.plot_image(self.min, self.max)
474 self.writeStatusBar("New range applied.")
476 def on_prevImageButton_clicked(self, widget, *args):
477 print 'prev'
479 # if we are at the beginning of the list do nothing
480 if self.file_index == 0: return
482 if self.file_index == -1:
483 # if the file loaded was not on the list load the first
484 self.file_index = 0
485 else:
486 self.file_index -=1
488 self.load_image(self.file_list[self.file_index])
491 def on_nextImageButton_clicked(self, widget, *args):
492 print 'next'
494 # if we are at the end of the list do nothing
495 if self.file_index == len(self.file_list) - 1: return
497 if self.file_index == -1:
498 # if the file loaded was not on the list load the first
499 self.file_index = 0
500 else:
501 self.file_index +=1
503 self.load_image(self.file_list[self.file_index])
506 def on_applyButton_clicked(self, widget, *args):
507 min = self.minSpinButton.get_value()
508 max = self.maxSpinButton.get_value()
509 if min >= max:
510 self.minSpinButton.set_value(self.min)
511 self.maxSpinButton.set_value(self.max)
512 self.writeStatusBar("Invalid range value: must be Min < Max.")
513 elif self.min != min or self.max != max:
514 self.min, self.max = min, max
515 self.plot_image(self.min, self.max)
516 self.writeStatusBar("New range applied.")
518 def on_measureButton_toggled(self, widget, data=None):
519 self.measuring = not self.measuring
520 if self.measuring:
521 # Create the list of colors the first time
522 if self.colorlist == None:
523 self.colorlist = CircularList(len(self.colors))
524 for c in self.colors:
525 self.colorlist.append(colorConverter.to_rgba(c))
526 # Connect the cb to handle the measure
527 self.cid = self.canvas.mpl_connect('button_release_event',
528 self.on_canvas_clicked)
529 self.statusBar.push(self.context,
530 '"Measure Length" tool enabled: select the first point.')
531 else:
532 self.statusBar.push(self.context,
533 '"Measure Length" tool disabled.')
534 self.canvas.mpl_disconnect(self.cid)
537 # The following are menu callbacks
540 ## File
541 def on_openfile_activate(self, widget, *args):
542 if self.openfileDialog is None:
543 self.openfileDialog = OpenFileDialog(self)
544 else:
545 self.openfileDialog.window.show()
547 def on_savefile_activate(self, widget, *args):
548 if self.savefileDialog is None:
549 self.savefileDialog = SaveFileDialog(self)
550 else:
551 self.savefileDialog.window.show()
553 def on_reload_activate(self, widget, *args):
554 dprint ("Reloading image ...")
555 if self.filename != None:
556 self.on_clearsegments_activate(None, None)
557 self.load_image(self.filename)
559 def on_close_activate(self, widget, *args):
560 self.figure.clf()
561 self.canvas.draw()
562 self.image_loaded = False
564 def on_quit_activate(self, widget, *args):
565 self.on_imagewindow_destroy(widget, *args)
567 ## Modify
568 def on_scrollingmean_activate(self, widget, *args):
569 if not self.isColorImage:
570 print 'Type:', self.image_array.dtype.name
571 self.image_array = self.image_array.astype('float32')
572 print 'Convolution spatial filter ... '
573 kernel = array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype='float32')
574 kernel /= kernel.sum()
575 print ' - Convolution kernel: \n', kernel
576 self.image_array = filters.convolve(self.image_array, kernel)
577 self.imagemode = 'floatarray'
578 self.plot_image()
579 print 'OK\n'
580 else:
581 self.statusBar.push(self.context,
582 "This mean can't be performed on color images.")
584 def on_binning_activate(self, widget, *args):
585 BinningDialog(self)
587 def on_gaussianFilter_activate(self, widget, *args):
588 dprint ("Gaussian Filer")
589 GaussianFilterWindow(self)
591 def on_clearsegments_activate(self, widget, *args):
592 if self.segments == []: return
593 if self.sectionApp != None:
594 self.sectionApp.destroy()
595 self.sectionApp = None
596 self.segments = []
597 self.linescolors = []
598 self.colorlist.resetIndex()
599 self.sectionsLC = None
600 #self.axim.clear()
601 self.plot_image()
603 def on_resetrange_activate(self, widget, *args):
604 self.plot_image(
605 min=int(self.image_array.min()),
606 max=int(self.image_array.max()))
608 def on_pixelDimension_activate(self, widget, *args):
609 PixelDimensionWindow(self)
611 def on_title_and_axes_labels_activate(self, widget, *args):
612 TitleAndAxesWindow(self)
614 ## View
615 def on_downup_activate(self, widget, *args):
616 if self.origin == 'upper': self.origin = 'lower'
617 else: self.origin = 'upper'
618 if self.image_loaded: self.plot_image()
619 if self.sectionApp != None:
620 print " *** WARNING: swap of segments not implemented."
622 def on_grid_activate(self, widget, data=None):
623 if self.gridon: self.axim.grid(False) # Toggle ON -> OFF
624 else: self.axim.grid(color=self.gridcolor) # Toggle OFF -> ON
625 self.canvas.draw()
626 self.gridon = not self.gridon
628 def on_whitegrid_activate(self, widget, *args):
629 if self.gridcolor == 'white': self.gridcolor = 'black'
630 else: self.gridcolor = 'white'
631 dprint ("grid color:", self.gridcolor)
633 if self.gridon:
634 self.axim.grid(self.gridon, color=self.gridcolor)
635 self.canvas.draw()
637 def on_sectionlist_activate(self, widget, *args):
638 colors = CircularList(len(self.colors))
639 colors.fromlist(self.colors)
640 text = ''
641 for n, seg in enumerate(self.segments):
642 (x1, y1), (x2, y2) = seg
643 lenAU = self.calculateDistance(seg, False)
644 text += '== Section '+ str(n+1) +' ('+ colors.get(n)+') ==\n'
645 text += 'x1 = %4d y1 = %4d\n' % (x1, y1)
646 text += 'x2 = %4d y2 = %4d\n' % (x2, y2)
647 text += 'Length: %5.2f, with pixel dimention (%2.2f x %2.2f).\n'\
648 % (lenAU, self.xPixelDim, self.yPixelDim)
649 text += '\n'
650 SectionListApp(text)
652 def on_sections_activate(self, widget, *args):
653 if self.isColorImage:
654 self.statusBar.push(self.context,
655 'Function not available for color images.')
656 elif self.segments == []:
657 self.statusBar.push(self.context, 'No section to plot.')
658 elif self.sectionApp == None:
659 for index, color in enumerate(self.linescolors):
660 dprint (index, color, "cycle")
661 aSection = self.calculateSection(index)
662 print 'color', aSection.color
663 if self.sectionApp == None:
664 self.sectionApp = SectionApp(gladefile, aSection, self)
665 else:
666 self.sectionApp.addsection(aSection)
667 else:
668 self.sectionApp.window.show()
670 ## Calculate
671 def on_peakintegral_activate(self, widget, *args):
672 if self.PeakIntegralDialog is None:
673 self.PeakIntegralDialog = PeakIntegralDialog(self)
674 else:
675 self.PeakIntegralDialog.window.show()
677 def on_sum_by_activate(self, widget, *args):
678 SumByDialog(self)
680 def on_multiply_by_activate(self, widget, *args):
681 MultiplyByDialog(self)
683 def on_image_properties_activate(self, widget, *args):
684 ImagePropertiesDialog(self)
686 ## About
687 def on_information_activate(self, widget, *args):
688 about = AboutDialog()
692 # Dialogs Classes
694 class SaveFileDialog(GenericSaveFileDialog):
696 This class implements the "Save File" dialog for the main window.
698 def __init__(self, callerApp):
699 GenericSaveFileDialog.__init__(self, 'savefileDialog',
700 gladefile, callerApp)
702 def saveToSelectedFile(self):
703 filename = self.filename + '.pickle'
704 try:
705 f = open(filename, 'w')
706 cPickle.dump(self.callerApp.image_array, f)
707 f.close()
708 except:
709 self.callerApp.writeStatusBar("Cannot save to file '%s'." % filename)
710 else:
711 self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
712 self.callerApp.window.set_title(os.path.basename(filename))
713 self.callerApp.filename = filename
715 self.window.hide()
717 class OpenFileDialog(DialogWindowWithCancel):
719 This class implements the "Open File" dialog.
721 def __init__(self, callerApp):
722 DialogWindowWithCancel.__init__(self, 'openfileDialog',
723 gladefile, callerApp)
725 def openSelectedFile(self):
726 filename = self.window.get_filename()
727 if filename != None:
728 self.window.hide()
729 self.callerApp.load_files(filename, os.path.dirname(filename))
730 self.window.hide()
732 def on_openButton_clicked(self, widget, *args):
733 dprint ("open button clicked", self.window.get_filename())
734 self.openSelectedFile()
736 def on_openfileDialog_file_activated(self, widget, *args):
737 dprint ("Open File Dialog: file_activated", self.window.get_filename())
739 def on_openfileDialog_response(self, widget, *args):
740 dprint ("\nOpen File Dialog: response event")
742 def on_cancelButton_clicked(self, widget, *args):
743 # Don't destroy the dialog so we remember the folder next time
744 self.window.hide()
745 return True
747 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
749 class GaussianFilterWindow(DialogWindowWithCancel):
751 Dialog for setting the gaussian filter options.
753 def __init__(self, callerApp):
754 DialogWindowWithCancel.__init__(self, 'GaussianFilterWindow',
755 gladefile, callerApp)
756 self.sigmaSpinButton = self.widgetTree.get_widget('sigmaSpinButton')
758 def on_okButton_clicked(self, widget, *args):
759 sigma = self.sigmaSpinButton.get_value()
760 dprint ("Ok, sigma =", sigma)
761 self.callerApp.image_array = array(filters.gaussian_filter(
762 self.callerApp.image_array, sigma))
763 self.callerApp.imagemode = 'floatarray'
764 self.callerApp.plot_image()
765 self.window.destroy()
767 class BinningDialog(DialogWindowWithCancel):
769 Dialog for setting the binning parameters.
771 def __init__(self, callerApp):
772 DialogWindowWithCancel.__init__(self, 'BinningDialog',
773 gladefile, callerApp)
775 self.xBinSpinButt = self.widgetTree.get_widget('xBinSpinButt')
776 self.yBinSpinButt = self.widgetTree.get_widget('yBinSpinButt')
778 def on_okButton_clicked(self, widget, *args):
779 dprint ("OK")
780 xb = int(self.xBinSpinButt.get_value())
781 yb = int(self.yBinSpinButt.get_value())
782 a = self.callerApp.image_array
783 self.callerApp.image_array = digital_binning(a, xb, yb)
784 self.callerApp.autoset_extent()
785 a = self.callerApp.image_array
786 self.callerApp.plot_image(min=int(a.min()), max=int(a.max()))
787 self.window.destroy()
789 class PixelDimensionWindow(DialogWindowWithCancel):
791 Dialog for setting the pixel dimension.
793 def __init__(self, callerApp):
794 DialogWindowWithCancel.__init__(self, 'PixelDimensionWindow',
795 gladefile, callerApp)
797 xDim = self.callerApp.xPixelDim
798 yDim = self.callerApp.yPixelDim
800 self.xPixDimSpinButt = self.widgetTree.get_widget('xPixDimSpinButt')
801 self.yPixDimSpinButt = self.widgetTree.get_widget('yPixDimSpinButt')
802 self.aspectSpinButt = self.widgetTree.get_widget('aspectSpinButt')
803 self.xPixDimSpinButt.set_value(xDim)
804 self.yPixDimSpinButt.set_value(yDim)
805 self.aspectSpinButt.set_value(self.callerApp.aspect)
807 def on_okButton_clicked(self, widget, *args):
808 dprint ("OK")
809 self.callerApp.xPixelDim = self.xPixDimSpinButt.get_value()
810 self.callerApp.yPixelDim = self.yPixDimSpinButt.get_value()
811 self.callerApp.aspect = self.aspectSpinButt.get_value()
812 self.callerApp.autoset_extent()
813 self.callerApp.plot_image()
814 self.window.destroy()
816 class TitleAndAxesWindow(DialogWindowWithCancel):
818 Dialog for setting Title and axes labels.
820 def __init__(self, callerApp):
821 DialogWindowWithCancel.__init__(self, 'TitleAndAxesWindow',
822 gladefile, callerApp)
824 self.titleEntry = self.widgetTree.get_widget('titleEntry')
825 self.xlabelEntry = self.widgetTree.get_widget('xlabelEntry')
826 self.ylabelEntry = self.widgetTree.get_widget('ylabelEntry')
828 def sync(field, entry):
829 if field != None: entry.set_text(field)
831 sync(self.callerApp.title, self.titleEntry)
832 sync(self.callerApp.xlabel, self.xlabelEntry)
833 sync(self.callerApp.ylabel, self.ylabelEntry)
835 def on_okButton_clicked(self, widget, *args):
836 dprint ("Ok clicked")
837 self.callerApp.axim.set_title(self.titleEntry.get_text())
838 self.callerApp.axim.set_xlabel(self.xlabelEntry.get_text())
839 self.callerApp.axim.set_ylabel(self.ylabelEntry.get_text())
840 self.callerApp.canvas.draw()
842 self.callerApp.title = self.titleEntry.get_text()
843 self.callerApp.xlabel = self.xlabelEntry.get_text()
844 self.callerApp.ylabel = self.ylabelEntry.get_text()
846 self.window.destroy()
848 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
850 class SectionListApp(GenericSecondaryWindow):
852 This class implement the text window used to show the sections' list.
854 def __init__(self, text):
855 GenericSecondaryWindow.__init__(self, 'SectionListWindow', gladefile)
857 textview = self.widgetTree.get_widget('sectionsTextView')
858 buffer = textview.get_buffer()
859 buffer.set_text(text)
861 def on_closeButton_clicked(self, widget, data=None):
862 self.window.destroy()
864 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
866 class PeakIntegralDialog(DialogWindowWithCancel):
868 Dialog for calculating the integral of a peak.
870 def __init__(self, callerApp):
871 DialogWindowWithCancel.__init__(self, 'PeakIntegralDialog',
872 gladefile, callerApp)
874 self.contour = None
875 self.circle = None
876 self.allowReplot = False
877 self.yc, self.xc = None, None
878 self.t = self.rt = None
879 self.OutputDialog = None
880 self.cid = None
882 self.image = self.callerApp.image_array
883 self.ax = self.callerApp.axim
884 self.canvas = self.callerApp.canvas
885 self.origin = self.callerApp.origin
887 self.gui_setup()
888 self.allowReplot = True
889 self.update_all()
891 def gui_setup(self):
892 ## Load the GUI widgets
893 widgets = ['thresholdSpinButt', 'rThresholdSpinButt',
894 'maxRadiusSpinButt', 'xCircleSpinButt', 'yCircleSpinButt',
895 'circleFrame', 'thresholdFrame',
896 'circleCheckButt', 'thresholdCheckButt']
898 for w_name in widgets:
899 setattr(self, w_name, self.widgetTree.get_widget(w_name))
900 if getattr(self, w_name) is None:
901 print " *** ERROR: No GUI object found for '%s'" % w_name
903 ## Connect the click event to select the circle center
904 self.connect_mouse()
906 ## Set default values
907 t = (self.image.max() - self.image.min())*0.1 + self.image.min()
908 self.thresholdSpinButt.set_value(t)
910 r = self.image.shape[0] / 8
911 self.maxRadiusSpinButt.set_value(r)
913 self.yc, self.xc = M.maximum_position(self.image)
914 if self.origin == 'upper':
915 dprint('upper origin')
916 self.yc = self.image.shape[0] - self.yc
918 self.xCircleSpinButt.set_value(self.xc)
919 self.yCircleSpinButt.set_value(self.yc)
921 self.circleFrame.set_sensitive(self.circleCheckButt.get_active())
922 self.thresholdFrame.set_sensitive(self.thresholdCheckButt.get_active())
924 def calculate_integral(self):
925 dprint('Calculating integral')
926 a, t, rt, xc, yc, r = self.image, self.t, self.rt, self.xc, self.yc, \
927 self.r
929 if self.origin == 'upper':
930 yc = self.image.shape[0] - yc
932 if self.thresholdCheckButt.get_active():
933 t, rt = 0., 0.
935 if self.circleCheckButt.get_active():
936 xc, yc, r = 0., 0., 0.
938 raw_integral, domain_size, raw_residual, offset_integral, offset = \
939 peak_integral2t(a, t, rt, xc, yc, r,
940 self.circleCheckButt.get_active(),
941 self.thresholdCheckButt.get_active())
943 data = "Raw Integral: \t %d \n" % (raw_integral)
944 data += "Integrated pixels: \t %d \n" % (domain_size)
945 data += "Raw Residual: \t %d \n" % (raw_residual)
946 data += "Residual pixels: \t %d \n" % (a.size - domain_size)
947 data += "Integral - offset: \t %1.2f \n" % (offset_integral)
948 data += "Offset: \t\t %1.4f \n" % (offset)
950 val = [t, rt, xc, yc, r]
951 for i,v in enumerate(val):
952 if v is None: val[i] = 0.
953 data += "t, rt, xc, yc, r: \t %1.1f, %1.1f, %1.1f, %1.1f, %1.1f\n\n" \
954 % tuple(val)
956 if self.OutputDialog is None:
957 self.OutputDialog = OutputDialog(data)
958 else:
959 self.OutputDialog.add_text(data)
960 self.OutputDialog.window.show()
962 # Plotting methods
964 def get_cursor_cb(self, event):
965 if event.inaxes:
966 dprint('setting new center')
967 self.allowReplot = False
968 self.xCircleSpinButt.set_value(event.xdata)
969 self.allowReplot = True
970 self.yCircleSpinButt.set_value(event.ydata)
971 self.canvas.draw()
973 def update_all(self):
974 self.update_circle()
975 self.update_mask()
976 self.update_contour()
977 self.canvas.draw()
979 def update_mask(self):
980 if self.thresholdCheckButt.get_active():
981 self.t = self.thresholdSpinButt.get_value()
982 self.rt = self.rThresholdSpinButt.get_value()
984 if self.t > 0:
985 mask = self.image > self.t
986 else:
987 mask = True
989 if self.t > 0:
990 rmask = self.image > self.rt
991 else:
992 rmask = True
994 if self.circleCheckButt.get_active():
995 r, xc, yc = [ getattr(self, a).get_value() for a in [
996 'maxRadiusSpinButt', 'xCircleSpinButt', 'yCircleSpinButt']]
998 # Creating distance function using numpy broadcasting rules
999 x = arange(self.image.shape[1])
1000 y = arange(self.image.shape[0]).reshape(self.image.shape[0],1)
1001 if self.origin == 'upper':
1002 yc = self.image.shape[0] - yc
1003 distance = sqrt((x-xc)**2 + (y-yc)**2)
1004 self.mask = logical_or(logical_and(mask, distance < r),
1005 logical_and(rmask, distance > r))
1006 else:
1007 self.mask = mask
1009 def update_contour(self):
1010 if self.thresholdCheckButt.get_active() and self.mask is not True:
1011 if self.contour is not None:
1012 self.delete_contour()
1013 self.contour = self.ax.contourf(self.mask, [-1, 0.5],
1014 colors=['black'], alpha=0.7, origin=self.origin)
1016 def update_circle(self):
1017 if self.circleCheckButt.get_active():
1018 self.r = self.maxRadiusSpinButt.get_value()
1019 self.xc = self.xCircleSpinButt.get_value()
1020 self.yc = self.yCircleSpinButt.get_value()
1022 if self.allowReplot:
1023 x, y = circle(self.xc, self.yc, self.r)
1025 if self.circle is None:
1026 self.circle, = self.ax.plot(x, y,
1027 lw=2, color='white', linestyle='--')
1028 else:
1029 self.circle.set_data(x,y)
1031 self.ax.set_xlim(0,self.image.shape[1])
1032 self.ax.set_ylim(0,self.image.shape[0])
1034 def delete_contour(self):
1035 self.callerApp.plot_image()
1036 self.circle = None
1037 self.update_circle()
1038 self.contour = None
1040 def delete_circle(self):
1041 if self.circle is not None:
1042 self.circle.set_data([],[])
1044 def disconnect_mouse(self):
1045 if self.cid is not None:
1046 self.ax.figure.canvas.mpl_disconnect(self.cid)
1047 self.cid = None
1049 def connect_mouse(self):
1050 if self.cid is None:
1051 self.cid = self.ax.figure.canvas.mpl_connect(
1052 'button_release_event', self.get_cursor_cb)
1053 self.callerApp.writeStatusBar(
1054 "Click on the image to select the center of integration.")
1057 # GUI callbacks
1059 def on_radiusCheckButt_toggled(self, widget, *args):
1060 dprint ("radius toggled")
1061 use_radius = widget.get_active()
1062 self.circleFrame.set_sensitive(use_radius)
1064 if use_radius:
1065 self.connect_mouse()
1066 self.update_circle()
1067 self.update_mask()
1068 self.update_contour()
1069 else:
1070 self.disconnect_mouse()
1071 self.delete_circle()
1072 self.update_mask()
1073 self.update_contour()
1074 self.canvas.draw()
1076 def on_thresholdCheckButt_toggled(self, widget, *args):
1077 dprint ("threshold toggled")
1078 use_threshold = widget.get_active()
1079 self.thresholdFrame.set_sensitive(use_threshold)
1081 if use_threshold:
1082 self.update_mask()
1083 self.update_contour()
1084 else:
1085 self.delete_contour()
1086 self.canvas.draw()
1088 def on_thresholdSpinButt_value_changed(self, widget, *args):
1089 dprint ("threshold value changed")
1090 if self.allowReplot:
1091 self.update_mask()
1092 self.update_contour()
1093 self.canvas.draw()
1095 def on_rThresholdSpinButt_value_changed(self, widget, *args):
1096 dprint ("residual threshold value changed")
1097 if self.allowReplot:
1098 self.update_mask()
1099 self.update_contour()
1100 self.canvas.draw()
1103 def on_maxRadiusSpinButt_value_changed(self, widget, *args):
1104 dprint ("radious value changed")
1105 if self.allowReplot:
1106 self.update_mask()
1107 self.update_contour()
1108 self.update_circle()
1109 self.canvas.draw()
1111 def on_xCircleSpinButt_value_changed(self, widget, *args):
1112 dprint ("x coord value changed")
1113 if self.allowReplot:
1114 self.update_mask()
1115 self.update_contour()
1116 self.update_circle()
1117 self.canvas.draw()
1119 def on_yCircleSpinButt_value_changed(self, widget, *args):
1120 dprint ("y coord value changed")
1121 if self.allowReplot:
1122 self.update_mask()
1123 self.update_contour()
1124 self.update_circle()
1125 self.canvas.draw()
1127 def on_calculateButton_clicked(self, widget, *args):
1128 dprint ("Calculate")
1129 self.calculate_integral()
1131 def on_okButton_clicked(self, widget, *args):
1132 dprint ("OK")
1133 self.calculate_integral()
1134 self.callerApp.plot_image()
1135 self.window.destroy()
1137 def on_destroy(self, widget, *args):
1138 dprint ("destroing dialog")
1139 self.callerApp.plot_image()
1140 self.callerApp.PeakIntegralDialog = None
1141 self.window.destroy()
1143 class ImagePropertiesDialog(ChildWindow):
1145 Dialog to calculate image properties.
1147 def __init__(self, callerApp):
1148 ChildWindow.__init__(self, 'ImagePropertiesDialog', gladefile,
1149 callerApp)
1151 self.widgetTree.get_widget('fnameEntry').set_text(
1152 os.path.basename(self.callerApp.filename))
1153 self.widgetTree.get_widget('pathEntry').set_text(
1154 os.path.abspath(os.path.dirname(self.callerApp.filename)))
1155 self.widgetTree.get_widget('sizeEntry').set_text("%3.1f KB" %\
1156 (os.stat(self.callerApp.filename).st_size/1024.,))
1158 ## Load the GUI widgets
1159 widgets = ['nPixelsEntry', 'shapeLabel', 'maxEntry', 'maxPosLabel',
1160 'minEntry', 'minPosLabel', 'varEntry', 'stdEntry', 'sumEntry',
1161 'meanEntry', 'medianEntry', 'modeEntry']
1163 for w_name in widgets:
1164 setattr(self, w_name, self.widgetTree.get_widget(w_name))
1165 if getattr(self, w_name) is None:
1166 print " *** ERROR: No GUI object found for '%s'" % w_name
1168 im = self.callerApp.image_array
1169 self.nPixelsEntry.set_text(str(im.size))
1170 self.shapeLabel.set_text("%d x %d" % im.shape[::-1])
1171 self.maxEntry.set_text(str(im.max()))
1172 self.maxPosLabel.set_text("@ %d x %d" % M.maximum_position(im)[::-1])
1173 self.minEntry.set_text(str(im.min()))
1174 self.minPosLabel.set_text("@ %d x %d" % M.minimum_position(im)[::-1])
1175 self.varEntry.set_text("%1.3f" % im.var())
1176 self.stdEntry.set_text("%1.3f" % im.std())
1177 self.sumEntry.set_text("%d" % im.sum())
1178 self.meanEntry.set_text("%1.3f" % im.mean())
1179 self.medianEntry.set_text("%1.2f" %(im.min() + (im.max()-im.min())*0.5))
1180 self.modeEntry.set_text('Not yet implemented')
1181 self.modeEntry.set_sensitive(False)
1183 def on_closeButton_clicked(self, widget, *args):
1184 self.window.destroy()
1186 class OperatorDialogSpinButt(DialogWindowWithCancel):
1188 Generic class for a dialog with a spin button that applies an operator to
1189 the image.
1191 def __init__(self, name, SpinButtonName, gladefile, callerApp):
1192 ChildWindow.__init__(self, name, gladefile, callerApp)
1193 self.SpinButt = self.widgetTree.get_widget(SpinButtonName)
1194 self.old_image = self.callerApp.image_array.copy()
1195 self.callerApp.image_array = self.callerApp.image_array.astype('float')
1197 def calculate(self):
1198 raise NotImplementedError
1200 def on_SpinButt_value_changed(self, widget, *args):
1201 dprint('SpinButt value changed')
1202 self.calculate()
1203 self.callerApp.plot_image()
1205 def on_applyButton_clicked(self, widget, *args):
1206 self.calculate()
1207 self.callerApp.plot_image()
1209 def on_okButton_clicked(self, widget, *args):
1210 self.calculate()
1211 self.callerApp.plot_image()
1212 self.window.destroy()
1214 def on_cancelButton_clicked(self, widget, *args):
1215 self.callerApp.image_array = self.old_image
1216 self.callerApp.plot_image()
1217 self.window.destroy()
1219 class SumByDialog(OperatorDialogSpinButt):
1221 Dialog to sum a value to the image.
1223 def __init__(self, callerApp):
1224 OperatorDialogSpinButt.__init__(self, 'SumByDialog', 'sumBySpinButt',
1225 gladefile, callerApp)
1227 def calculate(self):
1228 self.callerApp.image_array += self.SpinButt.get_value()
1230 class MultiplyByDialog(OperatorDialogSpinButt):
1231 """
1232 Dialog to multiply a value to the image.
1234 def __init__(self, callerApp):
1235 OperatorDialogSpinButt.__init__(self, 'MultiplyByDialog',
1236 'multBySpinButt', gladefile, callerApp)
1238 def calculate(self):
1239 self.callerApp.image_array *= self.SpinButt.get_value()
1241 class SaveOutputDialog(GenericSaveFileDialog):
1243 This class implements a "Save File" dialog for the output of a dialog.
1245 def __init__(self, data, callerApp=None):
1246 GenericSaveFileDialog.__init__(self, 'savefileDialog',
1247 gladefile, callerApp)
1248 self.data = data
1250 def saveToSelectedFile(self):
1251 filename = self.filename
1252 try:
1253 f = open(filename, 'w')
1254 f.write(self.data)
1255 f.close()
1256 #self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
1257 except:
1258 #self.callerApp.writeStatusBar("Can't save file '%s'." % (filename))
1259 pass
1260 self.window.hide()
1262 class OutputDialog(GenericSecondaryWindow):
1264 This class implements a dialog to output some text.
1266 def __init__(self, text):
1267 GenericSecondaryWindow.__init__(self, 'OutputDialog', gladefile)
1268 textview = self.widgetTree.get_widget('textview')
1269 self.buffer = textview.get_buffer()
1270 self.buffer.set_text(text)
1271 self.data = text
1273 def add_text(self, text):
1274 self.data += text
1275 self.buffer.set_text(self.data)
1277 def on_saveButton_clicked(self, widget, *args):
1278 dprint ("clear clicked")
1279 SaveOutputDialog(self.data)
1281 def on_clearButton_clicked(self, widget, *args):
1282 dprint ("save clicked")
1283 self.data=''
1284 self.buffer.set_text('')
1286 def on_closeButton_clicked(self, widget, *args):
1287 dprint ("close clicked")
1288 self.window.hide()
1290 def on_delete_event(self, widget, *args):
1291 dprint ("delete signal")
1292 self.window.hide()
1293 return True
1295 def on_destroy(self, widget, *args):
1296 dprint ("destroy signal")
1297 self.window.destroy()
1299 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1301 class AboutDialog(GenericSecondaryWindow):
1303 Object for the "About Dialog".
1305 def __init__(self):
1306 GenericSecondaryWindow.__init__(self, 'aboutDialog', gladefile)
1307 self.window.set_version(read_version(rootdir))
1308 self.window.connect("response", lambda d, r: d.destroy())
1311 def generate_file_list():
1312 import glob
1314 extensions = ['jpg', 'jpeg', 'png', 'tif', 'tiff']
1315 file_list = []
1316 for e in extensions:
1317 for ee in [e.lower(), e.upper(), e.title()]:
1318 file_list += glob.glob('*.'+ee)
1320 return file_list
1323 def main():
1324 file_name = sourcedir + 'samples/test-img.tif'
1325 files = sys.argv[1:]
1326 if len(files) < 1: files =(file_name,)
1327 generate_file_list()
1328 for filename in files:
1329 try:
1330 file = open(filename,'r')
1331 ia = ImageApp(filename)
1332 file.close()
1333 except IOError:
1334 print "\n File '"+filename+"' is not existent or not readable.\n"
1335 ia = ImageApp()
1336 ia.start()
1338 if __name__ == "__main__": main()