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.
20 rootdir
= os
.path
.abspath(imp
.find_module('pyplotsuite')[1]) + '/'
21 sourcedir
= rootdir
+ 'imageanalyzer/'
22 gladefile
= sourcedir
+ 'image_analyzer.glade'
30 print '\n You need to install the Python Imaging Library (PIL) module. \n'
33 # Generic window description classes
34 from pyplotsuite
.common
.gtk_generic_windows
import \
35 GenericMainPlotWindow
, MyNavigationToolbar
, GenericSecondaryWindow
, \
36 DialogWindowWithCancel
, GenericSaveFileDialog
, ChildWindow
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'
49 import scipy
.ndimage
.filters
as filters
50 if debug
: print 'Found scipy.ndimage.filters'
53 import numarray
.nd_image
.filters
as filters
54 if debug
: print 'Found numarray.nd_image.filters'
56 if debug
: print 'WARNING: No filters module found, filters disabled.'
59 # Search the optional module 'measurements'
60 has_measurements
= True
62 import scipy
.ndimage
.measurements
as M
63 print 'Found scipy.ndimage'
66 import numarray
.nd_image
.measurements
as M
67 print 'Found numarray.nd_image'
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
90 #profile.Profile.bias = 5e-6
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()
109 self
.filename
= filename
111 # Create additional attributes with default values
113 self
.origin
= 'lower' # Image origin ('upper' or 'lower')
114 self
.gridcolor
= 'white'
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',
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
135 self
.colorlist
= None
136 self
.sectionsLC
= None # LinesCollection representing the sections
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']
152 for ee
in [e
.lower(), e
.upper(), e
.title()]:
153 file_list
+= glob
.glob(current_path
+'/*.'+ee
)
155 print 'file_list: ', 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.')
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
])
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:
217 and optionally also self.image (the PIL image object).
220 self
.image_array
, self
.imagemode
, self
.image
= load_file(fname
)
221 except IOError, error_string
:
222 self
.statusBar
.push(self
.context
, error_string
)
224 except UnknownFileType
:
225 self
.statusBar
.push(self
.context
,
226 'File "%s" is of unknown type.' % fname
)
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
))
243 self
.axim
= self
.figure
.add_subplot(111)
245 if self
.imagemode
== 'L':
247 self
.isColorImage
= False
248 elif self
.imagemode
== 'I;16':
250 self
.isColorImage
= False
251 elif self
.imagemode
== 'floatarray':
253 self
.isColorImage
= False
256 self
.isColorImage
= True
258 if self
.isColorImage
:
259 self
.statusBar
.push(self
.context
,
260 'Image RGB(A): ignoring the range (min and max).')
262 self
.min = self
.image_array
.min()
263 self
.max = self
.image_array
.max()
264 if self
.imagemode
== 'floatarray':
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
295 self
.i
= self
.axim
.imshow(self
.image_array
,
296 interpolation
=self
.interp
,
297 vmin
=self
.min, vmax
=self
.max,
303 if not self
.isColorImage
:
306 # Try to plot the colorbar on an existing second axis
307 self
.figure
.colorbar(self
.i
, self
.figure
.axes
[1])
309 # Otherwise let colorbar() to create its default axis
310 self
.figure
.colorbar(self
.i
)
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
)
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
327 ly
, lx
= self
.image_array
.shape
328 self
.extent
= (0, lx
*self
.xPixelDim
, 0, ly
*self
.yPixelDim
)
330 def set_min(self
, min):
332 self
.minSpinButton
.set_value(min)
334 def set_max(self
, 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
)
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
)
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
385 dist
= sqrt(dx
**2 + dy
**2)
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
)
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
]
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.')
427 self
.i
.set_cmap(self
.colormap
)
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
)
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.')
443 elif self
.isColorImage
:
444 self
.statusBar
.push(self
.context
,
445 'Histogram view is not available for color images.')
448 if self
.histogramApp
== None:
449 dprint ("new histogram")
450 self
.histogramApp
= HistogramApp(gladefile
, self
, self
.nbit
,
453 dprint ("old histogram")
454 self
.histogramApp
.window
.show()
456 def on_maxSpinButton_value_changed(self
, widget
, *args
):
457 max = self
.maxSpinButton
.get_value()
459 self
.maxSpinButton
.set_value(self
.max)
460 self
.writeStatusBar("Invalid range value: must be Min < Max.")
461 elif 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()
469 self
.minSpinButton
.set_value(self
.min)
470 self
.writeStatusBar("Invalid range value: must be Min < Max.")
471 elif 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
):
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
488 self
.load_image(self
.file_list
[self
.file_index
])
491 def on_nextImageButton_clicked(self
, widget
, *args
):
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
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()
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
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.')
532 self
.statusBar
.push(self
.context
,
533 '"Measure Length" tool disabled.')
534 self
.canvas
.mpl_disconnect(self
.cid
)
537 # The following are menu callbacks
541 def on_openfile_activate(self
, widget
, *args
):
542 if self
.openfileDialog
is None:
543 self
.openfileDialog
= OpenFileDialog(self
)
545 self
.openfileDialog
.window
.show()
547 def on_savefile_activate(self
, widget
, *args
):
548 if self
.savefileDialog
is None:
549 self
.savefileDialog
= SaveFileDialog(self
)
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
):
562 self
.image_loaded
= False
564 def on_quit_activate(self
, widget
, *args
):
565 self
.on_imagewindow_destroy(widget
, *args
)
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'
581 self
.statusBar
.push(self
.context
,
582 "This mean can't be performed on color images.")
584 def on_binning_activate(self
, widget
, *args
):
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
597 self
.linescolors
= []
598 self
.colorlist
.resetIndex()
599 self
.sectionsLC
= None
603 def on_resetrange_activate(self
, widget
, *args
):
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
)
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
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
)
634 self
.axim
.grid(self
.gridon
, color
=self
.gridcolor
)
637 def on_sectionlist_activate(self
, widget
, *args
):
638 colors
= CircularList(len(self
.colors
))
639 colors
.fromlist(self
.colors
)
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
)
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
)
666 self
.sectionApp
.addsection(aSection
)
668 self
.sectionApp
.window
.show()
671 def on_peakintegral_activate(self
, widget
, *args
):
672 if self
.PeakIntegralDialog
is None:
673 self
.PeakIntegralDialog
= PeakIntegralDialog(self
)
675 self
.PeakIntegralDialog
.window
.show()
677 def on_sum_by_activate(self
, widget
, *args
):
680 def on_multiply_by_activate(self
, widget
, *args
):
681 MultiplyByDialog(self
)
683 def on_image_properties_activate(self
, widget
, *args
):
684 ImagePropertiesDialog(self
)
687 def on_information_activate(self
, widget
, *args
):
688 about
= AboutDialog()
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'
705 f
= open(filename
, 'w')
706 cPickle
.dump(self
.callerApp
.image_array
, f
)
709 self
.callerApp
.writeStatusBar("Cannot save to file '%s'." % filename
)
711 self
.callerApp
.writeStatusBar('File "%s" saved.' % (filename
))
712 self
.callerApp
.window
.set_title(os
.path
.basename(filename
))
713 self
.callerApp
.filename
= filename
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()
729 self
.callerApp
.load_files(filename
, os
.path
.dirname(filename
))
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
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
):
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
):
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
)
876 self
.allowReplot
= False
877 self
.yc
, self
.xc
= None, None
878 self
.t
= self
.rt
= None
879 self
.OutputDialog
= 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
888 self
.allowReplot
= True
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
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
, \
929 if self
.origin
== 'upper':
930 yc
= self
.image
.shape
[0] - yc
932 if self
.thresholdCheckButt
.get_active():
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" \
956 if self
.OutputDialog
is None:
957 self
.OutputDialog
= OutputDialog(data
)
959 self
.OutputDialog
.add_text(data
)
960 self
.OutputDialog
.window
.show()
964 def get_cursor_cb(self
, event
):
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
)
973 def update_all(self
):
976 self
.update_contour()
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()
985 mask
= self
.image
> self
.t
990 rmask
= self
.image
> self
.rt
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
))
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
='--')
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()
1037 self
.update_circle()
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
)
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.")
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
)
1065 self
.connect_mouse()
1066 self
.update_circle()
1068 self
.update_contour()
1070 self
.disconnect_mouse()
1071 self
.delete_circle()
1073 self
.update_contour()
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
)
1083 self
.update_contour()
1085 self
.delete_contour()
1088 def on_thresholdSpinButt_value_changed(self
, widget
, *args
):
1089 dprint ("threshold value changed")
1090 if self
.allowReplot
:
1092 self
.update_contour()
1095 def on_rThresholdSpinButt_value_changed(self
, widget
, *args
):
1096 dprint ("residual threshold value changed")
1097 if self
.allowReplot
:
1099 self
.update_contour()
1103 def on_maxRadiusSpinButt_value_changed(self
, widget
, *args
):
1104 dprint ("radious value changed")
1105 if self
.allowReplot
:
1107 self
.update_contour()
1108 self
.update_circle()
1111 def on_xCircleSpinButt_value_changed(self
, widget
, *args
):
1112 dprint ("x coord value changed")
1113 if self
.allowReplot
:
1115 self
.update_contour()
1116 self
.update_circle()
1119 def on_yCircleSpinButt_value_changed(self
, widget
, *args
):
1120 dprint ("y coord value changed")
1121 if self
.allowReplot
:
1123 self
.update_contour()
1124 self
.update_circle()
1127 def on_calculateButton_clicked(self
, widget
, *args
):
1128 dprint ("Calculate")
1129 self
.calculate_integral()
1131 def on_okButton_clicked(self
, widget
, *args
):
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
,
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
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')
1203 self
.callerApp
.plot_image()
1205 def on_applyButton_clicked(self
, widget
, *args
):
1207 self
.callerApp
.plot_image()
1209 def on_okButton_clicked(self
, widget
, *args
):
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
):
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
)
1250 def saveToSelectedFile(self
):
1251 filename
= self
.filename
1253 f
= open(filename
, 'w')
1256 #self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
1258 #self.callerApp.writeStatusBar("Can't save file '%s'." % (filename))
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
)
1273 def add_text(self
, 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")
1284 self
.buffer.set_text('')
1286 def on_closeButton_clicked(self
, widget
, *args
):
1287 dprint ("close clicked")
1290 def on_delete_event(self
, widget
, *args
):
1291 dprint ("delete signal")
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".
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():
1314 extensions
= ['jpg', 'jpeg', 'png', 'tif', 'tiff']
1316 for e
in extensions
:
1317 for ee
in [e
.lower(), e
.upper(), e
.title()]:
1318 file_list
+= glob
.glob('*.'+ee
)
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
:
1330 file = open(filename
,'r')
1331 ia
= ImageApp(filename
)
1334 print "\n File '"+filename
+"' is not existent or not readable.\n"
1338 if __name__
== "__main__": main()