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.
19 rootdir
= os
.path
.abspath(imp
.find_module('pyplotsuite')[1]) + '/'
20 sourcedir
= rootdir
+ 'imageanalyzer/'
21 gladefile
= sourcedir
+ 'image_analyzer.glade'
28 # Generic window description classes
29 from pyplotsuite
.common
.gtk_generic_windows
import \
30 GenericMainPlotWindow
, MyNavigationToolbar
, GenericSecondaryWindow
, \
31 DialogWindowWithCancel
, GenericSaveFileDialog
, ChildWindow
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'
44 import scipy
.ndimage
.filters
as filters
45 if debug
: print 'Found scipy.ndimage.filters'
48 import numarray
.nd_image
.filters
as filters
49 if debug
: print 'Found numarray.nd_image.filters'
51 if debug
: print 'WARNING: No filters module found, filters disabled.'
54 # Search the optional module 'measurements'
55 has_measurements
= True
57 import scipy
.ndimage
.measurements
as M
58 print 'Found scipy.ndimage'
61 import numarray
.nd_image
.measurements
as M
62 print 'Found numarray.nd_image'
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
85 #profile.Profile.bias = 5e-6
92 # Implementation Classes
94 class ImageApp(GenericMainPlotWindow
):
96 Main application window object.
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()
104 self
.filename
= filename
106 # Create additional attributes with default values
108 self
.origin
= 'upper' # Image origin ('upper' or 'lower')
109 self
.gridcolor
= 'white'
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',
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
130 self
.colorlist
= None
131 self
.sectionsLC
= None # LinesCollection representing the sections
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
)
142 self
.statusBar
.push(self
.context
,
143 'Use menu File -> Open to load an image.')
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:
185 and optionally also self.image (the PIL image object).
188 self
.image_array
, self
.imagemode
, self
.image
= load_file(fname
)
189 except IOError, error_string
:
190 self
.statusBar
.push(self
.context
, error_string
)
192 except UnknownFileType
:
193 self
.statusBar
.push(self
.context
,
194 'File "%s" is of unkown type.' % fname
)
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
)
211 self
.axim
= self
.figure
.add_subplot(111)
213 if self
.imagemode
== 'L':
215 self
.isColorImage
= False
216 elif self
.imagemode
== 'I;16':
218 self
.isColorImage
= False
219 elif self
.imagemode
== 'floatarray':
221 self
.isColorImage
= False
224 self
.isColorImage
= True
226 if self
.isColorImage
:
227 self
.statusBar
.push(self
.context
,
228 'Image RGB(A): ignoring the range (min and max).')
230 self
.min = self
.image_array
.min()
231 self
.max = self
.image_array
.max()
232 if self
.imagemode
== 'floatarray':
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
263 self
.i
= self
.axim
.imshow(self
.image_array
,
264 interpolation
=self
.interp
,
265 vmin
=self
.min, vmax
=self
.max,
271 if not self
.isColorImage
:
274 # Try to plot the colorbar on an existing second axis
275 self
.figure
.colorbar(self
.i
, self
.figure
.axes
[1])
277 # Otherwise let colorbar() to create its default axis
278 self
.figure
.colorbar(self
.i
)
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
)
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
295 ly
, lx
= self
.image_array
.shape
296 self
.extent
= (0, lx
*self
.xPixelDim
, 0, ly
*self
.yPixelDim
)
298 def set_min(self
, min):
300 self
.minSpinButton
.set_value(min)
302 def set_max(self
, 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
)
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
)
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
353 dist
= sqrt(dx
**2 + dy
**2)
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
)
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
]
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.')
395 self
.i
.set_cmap(self
.colormap
)
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
)
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.')
411 elif self
.isColorImage
:
412 self
.statusBar
.push(self
.context
,
413 'Histogram view is not available for color images.')
416 if self
.histogramApp
== None:
417 dprint ("new histogram")
418 self
.histogramApp
= HistogramApp(gladefile
, self
, self
.nbit
,
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()
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
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.')
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
)
461 self
.openfileDialog
.window
.show()
463 def on_savefile_activate(self
, widget
, *args
):
464 if self
.savefileDialog
is None:
465 self
.savefileDialog
= SaveFileDialog(self
)
467 self
.savefileDialog
.window
.show()
469 def on_close_activate(self
, widget
, *args
):
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
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
)
496 self
.axim
.grid(self
.gridon
, color
=self
.gridcolor
)
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
):
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
)
525 self
.sectionApp
.addsection(aSection
)
527 self
.sectionApp
.window
.show()
529 def on_sectionlist_activate(self
, widget
, *args
):
530 colors
= CircularList(len(self
.colors
))
531 colors
.fromlist(self
.colors
)
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
)
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
550 self
.linescolors
= []
551 self
.colorlist
.resetIndex()
552 self
.sectionsLC
= None
556 def on_resetrange_activate(self
, widget
, *args
):
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'
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
)
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
):
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
):
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
)
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',
703 self
.callerApp
.writeStatusBar(
704 "Click on the image to select the center of integration.")
706 self
.thresholdSpinButt
= self
.widgetTree
.get_widget(
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
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
)
751 self
.OutputDialog
.add_text(data
)
752 self
.OutputDialog
.window
.show()
756 def get_cursor_cb(self
, event
):
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
)
765 def update_all(self
):
768 self
.update_contour()
771 def update_mask(self
):
772 self
.t
= self
.thresholdSpinButt
.get_value()
773 self
.rt
= self
.rThresholdSpinButt
.get_value()
776 mask
= self
.image
> self
.t
781 rmask
= self
.image
> self
.rt
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
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)
800 def update_contour(self
):
801 if self
.mask
is not True:
802 if self
.contour
is not None:
803 self
.callerApp
.plot_image()
807 self
.contour
= self
.ax
.contourf(self
.mask
, [-1, 0.5],
808 colors
=['black'], alpha
=0.7, origin
=self
.origin
)
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
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
='--')
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])
836 def on_thresholdSpinButt_value_changed(self
, widget
, *args
):
837 dprint ("threshold value changed")
840 self
.update_contour()
843 def on_rThresholdSpinButt_value_changed(self
, widget
, *args
):
844 dprint ("residual threshold value changed")
847 self
.update_contour()
851 def on_maxRadiousSpinButt_value_changed(self
, widget
, *args
):
852 dprint ("radious value changed")
857 def on_xCircleSpinButt_value_changed(self
, widget
, *args
):
858 dprint ("x coord value changed")
863 def on_yCircleSpinButt_value_changed(self
, widget
, *args
):
864 dprint ("y coord value changed")
869 def on_calculateButton_clicked(self
, widget
, *args
):
871 self
.calculate_integral()
873 def on_okButton_clicked(self
, widget
, *args
):
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
,
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'
974 f
= open(filename
, 'w')
975 cPickle
.dump(self
.callerApp
.image_array
, f
)
977 self
.callerApp
.writeStatusBar('File "%s" saved.' % (filename
))
979 self
.callerApp
.writeStatusBar("Can't save file '%s'." % (filename
))
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
)
991 def saveToSelectedFile(self
):
992 filename
= self
.filename
994 f
= open(filename
, 'w')
997 #self.callerApp.writeStatusBar('File "%s" saved.' % (filename))
999 #self.callerApp.writeStatusBar("Can't save file '%s'." % (filename))
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
)
1014 def add_text(self
, 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")
1025 self
.buffer.set_text('')
1027 def on_closeButton_clicked(self
, widget
, *args
):
1028 dprint ("close clicked")
1031 def on_delete_event(self
, widget
, *args
):
1032 dprint ("delete signal")
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:
1052 self
.callerApp
.load_image(filename
)
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
1070 class AboutDialog(GenericSecondaryWindow
):
1072 Object for the "About Dialog".
1075 GenericSecondaryWindow
.__init
__(self
, 'aboutDialog', gladefile
)
1076 self
.window
.set_version(read_version(rootdir
))
1077 self
.window
.connect("response", lambda d
, r
: d
.destroy())
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
:
1086 file = open(filename
,'r')
1087 ia
= ImageApp(filename
)
1090 print "\n File '"+filename
+"' is not existent or not readable.\n"
1094 if __name__
== "__main__": main()