~ubuntu-branches/ubuntu/trusty/guiqwt/trusty

« back to all changes in this revision

Viewing changes to guiqwt/tests/sift.py

  • Committer: Bazaar Package Importer
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2011-04-07 22:41:50 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20110407224150-kkhppnq7rp2c8b3c
Tags: 2.1.0-1
* Imported Upstream version 2.1.0
* debian/patches/
  - 0001-features-01_bts614083.patch (delete)
  - 0001-feature-fix-the-documentation-build.patch (new)
* add and install the sift desktop file
* recommends python-dicom (Closes: #621421)
  thanks Junqian Gordon Xu for the report

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright © 2010-2011 CEA
 
4
# Pierre Raybaut
 
5
# Licensed under the terms of the CECILL License
 
6
# (see guiqwt/__init__.py for details)
 
7
 
 
8
"""
 
9
SIFT, the Signal and Image Filtering Tool
 
10
Simple signal and image processing application based on guiqwt and guidata
 
11
"""
 
12
 
 
13
SHOW = True # Show test in GUI-based test launcher
 
14
 
 
15
from PyQt4.QtGui import (QMainWindow, QMessageBox, QSplitter, QListWidget,
 
16
                         QFileDialog, QVBoxLayout, QHBoxLayout, QWidget,
 
17
                         QTabWidget, QMenu, QApplication, QCursor, QFont)
 
18
from PyQt4.QtCore import Qt, QT_VERSION_STR, PYQT_VERSION_STR, SIGNAL
 
19
 
 
20
import sys, platform, os.path as osp, os
 
21
import numpy as np
 
22
 
 
23
from guidata.dataset.datatypes import DataSet, ValueProp
 
24
from guidata.dataset.dataitems import (IntItem, FloatArrayItem, StringItem,
 
25
                                       ChoiceItem, FloatItem, DictItem,
 
26
                                       BoolItem)
 
27
from guidata.dataset.qtwidgets import DataSetEditGroupBox
 
28
from guidata.configtools import get_icon
 
29
from guidata.qthelpers import create_action, add_actions, get_std_icon
 
30
from guidata.qtwidgets import DockableWidget, DockableWidgetMixin
 
31
from guidata.utils import update_dataset
 
32
 
 
33
from guiqwt.config import _
 
34
from guiqwt.plot import CurveWidget, ImageWidget
 
35
from guiqwt.builder import make
 
36
from guiqwt.io import (imagefile_to_array, IMAGE_LOAD_FILTERS,
 
37
                       array_to_imagefile, IMAGE_SAVE_FILTERS)
 
38
 
 
39
APP_NAME = _("Sift")
 
40
APP_DESC = _("""Signal and Image Filtering Tool<br>
 
41
Simple signal and image processing application based on guiqwt and guidata""")
 
42
VERSION = '0.2.2'
 
43
 
 
44
 
 
45
def xy_fft(x, y):
 
46
    """Compute FFT on X,Y data"""
 
47
    y1 = np.fft.fft(y)
 
48
    x1 = np.fft.fftshift(np.fft.fftfreq(x.shape[-1], d=x[1]-x[0]))
 
49
    return x1, y1
 
50
    
 
51
def xy_ifft(x, y):
 
52
    """Compute iFFT on X,Y data"""
 
53
    y1 = np.fft.ifft(y)
 
54
    x1 = np.fft.fftshift(np.fft.fftfreq(x.shape[-1], d=x[1]-x[0]))
 
55
    return x1, y1
 
56
    
 
57
def flatfield(rawdata, flatdata):
 
58
    """Compute flat-field correction"""
 
59
    dtemp = np.array(rawdata, dtype=np.float64, copy=True)*flatdata.mean()
 
60
    dunif = np.array(flatdata, dtype=np.float64, copy=True)
 
61
    dunif[dunif == 0] = 1.
 
62
    return np.array(dtemp/dunif, dtype=rawdata.dtype)
 
63
 
 
64
 
 
65
class SignalParam(DataSet):
 
66
    title = StringItem(_("Title"), default=_("Untitled"))
 
67
    xydata = FloatArrayItem(_("Data"), transpose=True, minmax="rows")
 
68
    def copy_data_from(self, other, dtype=None):
 
69
        self.xydata = np.array(other.xydata, copy=True, dtype=dtype)
 
70
    def change_data_type(self, dtype):
 
71
        self.xydata = np.array(self.xydata, dtype=dtype)
 
72
    def get_data(self):
 
73
        if self.xydata is not None:
 
74
            return self.xydata[1]
 
75
    def set_data(self, data):
 
76
        self.xydata[1] = data
 
77
    data = property(get_data, set_data)
 
78
 
 
79
class SignalParamNew(DataSet):
 
80
    title = StringItem(_("Title"), default=_("Untitled"))
 
81
    xmin = FloatItem("Xmin", default=-10.)
 
82
    xmax = FloatItem("Xmax", default=10.)
 
83
    size = IntItem(_("Size"), help=_("Signal size (total number of points)"),
 
84
                   min=1, default=500)
 
85
    type = ChoiceItem(_("Type"),
 
86
                      (("rand", _("random")), ("zeros", _("zeros")),
 
87
                       ("gauss", _("gaussian"))))
 
88
 
 
89
 
 
90
class ImageParam(DataSet):
 
91
    title = StringItem(_("Title"), default=_("Untitled"))
 
92
    data = FloatArrayItem(_("Data"))
 
93
    metadata = DictItem(_("Metadata"), default=None)
 
94
    def copy_data_from(self, other, dtype=None):
 
95
        self.data = np.array(other.data, copy=True, dtype=dtype)
 
96
    def change_data_type(self, dtype):
 
97
        self.data = np.array(self.data, dtype=dtype)
 
98
 
 
99
class ImageParamNew(DataSet):
 
100
    title = StringItem(_("Title"), default=_("Untitled"))
 
101
    height = IntItem(_("Height"), help=_("Image height (total number of rows)"),
 
102
                     min=1, default=500)
 
103
    width = IntItem(_("Width"), help=_("Image width (total number of columns)"),
 
104
                    min=1, default=500)
 
105
    dtype = ChoiceItem(_("Data type"),
 
106
                       ((np.uint8, "uint8"), (np.int16, "uint16"),
 
107
                        (np.float32, "float32"), (np.float64, "float64"),
 
108
                        ))
 
109
    type = ChoiceItem(_("Type"),
 
110
                      (("zeros", _("zeros")), ("empty", _("empty")),
 
111
                       ("rand", _("random")),
 
112
                        ))
 
113
 
 
114
 
 
115
class ObjectFT(QSplitter):
 
116
    """Object handling the item list, the selected item properties and plot"""
 
117
    PARAMCLASS = None
 
118
    PREFIX = None
 
119
    def __init__(self, parent, plot):
 
120
        super(ObjectFT, self).__init__(Qt.Vertical, parent)
 
121
        self.plot = plot
 
122
        self.objects = [] # signals or images
 
123
        self.items = [] # associated plot items
 
124
        self.listwidget = None
 
125
        self.properties = None
 
126
        self._hsplitter = None
 
127
        
 
128
        self.file_actions = None
 
129
        self.edit_actions = None
 
130
        self.operation_actions = None
 
131
        self.processing_actions = None
 
132
        
 
133
        self.number = 0
 
134
        
 
135
        self.directory = "" # last browsed directory
 
136
 
 
137
        # Object selection dependent actions
 
138
        self.actlist_1more = []
 
139
        self.actlist_2more = []
 
140
        self.actlist_1 = []
 
141
        self.actlist_2 = []
 
142
        
 
143
    #------Setup widget, menus, actions
 
144
    def setup(self, toolbar):
 
145
        self.listwidget = QListWidget()
 
146
        self.listwidget.setAlternatingRowColors(True)
 
147
        self.listwidget.setSelectionMode(QListWidget.ExtendedSelection)
 
148
        self.properties = DataSetEditGroupBox(_("Properties"), self.PARAMCLASS)
 
149
        self.properties.setEnabled(False)
 
150
 
 
151
        self.connect(self.listwidget, SIGNAL("currentRowChanged(int)"),
 
152
                     self.current_item_changed)
 
153
        self.connect(self.listwidget, SIGNAL("itemSelectionChanged()"),
 
154
                     self.selection_changed)
 
155
        self.connect(self.properties, SIGNAL("apply_button_clicked()"),
 
156
                     self.properties_changed)
 
157
        
 
158
        properties_stretched = QWidget()
 
159
        hlayout = QHBoxLayout()
 
160
        hlayout.addWidget(self.properties)
 
161
#        hlayout.addStretch()
 
162
        vlayout = QVBoxLayout()
 
163
        vlayout.addLayout(hlayout)
 
164
        vlayout.addStretch()
 
165
        properties_stretched.setLayout(vlayout)
 
166
        
 
167
        self.addWidget(self.listwidget)
 
168
        self.addWidget(properties_stretched)
 
169
 
 
170
        # Edit actions
 
171
        duplicate_action = create_action(self, _("Duplicate"),
 
172
                                         icon=get_icon('copy.png'),
 
173
                                         triggered=self.duplicate_object)
 
174
        self.actlist_1 += [duplicate_action]
 
175
        remove_action = create_action(self, _("Remove"),
 
176
                                      icon=get_icon('delete.png'),
 
177
                                      triggered=self.remove_object)
 
178
        self.actlist_1more += [remove_action]
 
179
        self.edit_actions = [duplicate_action, remove_action]
 
180
        
 
181
        # Operation actions
 
182
        sum_action = create_action(self, _("Sum"), triggered=self.compute_sum)
 
183
        average_action = create_action(self, _("Average"),
 
184
                                       triggered=self.compute_average)
 
185
        diff_action = create_action(self, _("Difference"),
 
186
                                    triggered=self.compute_difference)
 
187
        prod_action = create_action(self, _("Product"),
 
188
                                    triggered=self.compute_product)
 
189
        div_action = create_action(self, _("Division"),
 
190
                                   triggered=self.compute_division)
 
191
        self.actlist_2more += [sum_action, average_action, prod_action]
 
192
        self.actlist_2 += [diff_action, div_action]
 
193
        self.operation_actions = [sum_action, average_action,
 
194
                                  diff_action, prod_action, div_action]
 
195
 
 
196
    #------GUI refresh/setup
 
197
    def current_item_changed(self, row):
 
198
        if row != -1:
 
199
            update_dataset(self.properties.dataset, self.objects[row])
 
200
            self.properties.get()
 
201
 
 
202
    def _get_selected_rows(self):
 
203
        return [index.row() for index in
 
204
                self.listwidget.selectionModel().selectedRows()]
 
205
        
 
206
    def selection_changed(self):
 
207
        """Signal list: selection changed"""
 
208
        row = self.listwidget.currentRow()
 
209
        self.properties.setDisabled(row == -1)
 
210
        self.refresh_plot()
 
211
        nbrows = len(self._get_selected_rows())
 
212
        for act in self.actlist_1more:
 
213
            act.setEnabled(nbrows >= 1)
 
214
        for act in self.actlist_2more:
 
215
            act.setEnabled(nbrows >= 2)
 
216
        for act in self.actlist_1:
 
217
            act.setEnabled(nbrows == 1)
 
218
        for act in self.actlist_2:
 
219
            act.setEnabled(nbrows == 2)
 
220
            
 
221
    def make_item(self, row):
 
222
        raise NotImplementedError
 
223
        
 
224
    def update_item(self, row):
 
225
        raise NotImplementedError
 
226
        
 
227
    def refresh_plot(self):
 
228
        for item in self.items:
 
229
            if item is not None:
 
230
                item.hide()
 
231
        for row in self._get_selected_rows():
 
232
            item = self.items[row]
 
233
            if item is None:
 
234
                item = self.make_item(row)
 
235
                self.plot.add_item(item)
 
236
            else:
 
237
                self.update_item(row)
 
238
                self.plot.set_item_visible(item, True)
 
239
                self.plot.set_active_item(item)
 
240
        self.plot.do_autoscale()
 
241
        
 
242
    def refresh_list(self, new_current_row='current'):
 
243
        """new_current_row: integer, 'first', 'last', 'current'"""
 
244
        row = self.listwidget.currentRow()
 
245
        self.listwidget.clear()
 
246
        self.listwidget.addItems(["%s%03d: %s" % (self.PREFIX, i, obj.title)
 
247
                                  for i, obj in enumerate(self.objects)])
 
248
        if new_current_row == 'first':
 
249
            row = 0
 
250
        elif new_current_row == 'last':
 
251
            row = self.listwidget.count()-1
 
252
        elif isinstance(new_current_row, int):
 
253
            row = new_current_row
 
254
        else:
 
255
            assert new_current_row == 'current'
 
256
        if row < self.listwidget.count():
 
257
            self.listwidget.setCurrentRow(row)
 
258
        
 
259
    def properties_changed(self):
 
260
        """The properties 'Apply' button was clicked: updating signal"""
 
261
        row = self.listwidget.currentRow()
 
262
        update_dataset(self.objects[row], self.properties.dataset)
 
263
        self.refresh_list(new_current_row='current')
 
264
        self.listwidget.setCurrentRow(row)
 
265
        self.refresh_plot()
 
266
    
 
267
    def add_object(self, obj):
 
268
        self.objects.append(obj)
 
269
        self.items.append(None)
 
270
        self.refresh_list(new_current_row='last')
 
271
        self.listwidget.setCurrentRow(len(self.objects)-1)
 
272
        self.emit(SIGNAL('object_added()'))
 
273
        
 
274
    #------Edit operations
 
275
    def duplicate_object(self):
 
276
        row = self._get_selected_rows()[0]
 
277
        obj = self.objects[row]
 
278
        objcopy = self.PARAMCLASS()
 
279
        objcopy.title = obj.title
 
280
        objcopy.copy_data_from(obj)
 
281
        self.objects.insert(row+1, objcopy)
 
282
        self.items.insert(row+1, None)
 
283
        self.refresh_list(new_current_row=row+1)
 
284
        self.refresh_plot()
 
285
    
 
286
    def remove_object(self):
 
287
        rows = sorted(self._get_selected_rows(), reverse=True)
 
288
        for row in rows:
 
289
            self.objects.pop(row)
 
290
            item = self.items.pop(row)
 
291
            self.plot.del_item(item)
 
292
        self.refresh_list(new_current_row='first')
 
293
        self.refresh_plot()
 
294
        
 
295
    #------Operations
 
296
    def compute_sum(self):
 
297
        rows = self._get_selected_rows()
 
298
        sumobj = self.PARAMCLASS()
 
299
        sumobj.title = "+".join(["%s%03d" % (self.PREFIX, row) for row in rows])
 
300
        try:
 
301
            for row in rows:
 
302
                obj = self.objects[row]
 
303
                if sumobj.data is None:
 
304
                    sumobj.copy_data_from(obj)
 
305
                else:
 
306
                    sumobj.data += obj.data
 
307
        except Exception, msg:
 
308
            import traceback
 
309
            traceback.print_exc()
 
310
            QMessageBox.critical(self.parent(), APP_NAME,
 
311
                                 _(u"Error:")+"\n%s" % str(msg))
 
312
            return
 
313
        self.add_object(sumobj)
 
314
    
 
315
    def compute_average(self):
 
316
        rows = self._get_selected_rows()
 
317
        sumobj = self.PARAMCLASS()
 
318
        title = ", ".join(["%s%03d" % (self.PREFIX, row) for row in rows])
 
319
        sumobj.title = _("Average")+("(%s)" % title)
 
320
        original_dtype = self.objects[rows[0]].data.dtype
 
321
        try:
 
322
            for row in rows:
 
323
                obj = self.objects[row]
 
324
                if sumobj.data is None:
 
325
                    sumobj.copy_data_from(obj, dtype=np.float64)
 
326
                else:
 
327
                    sumobj.data += obj.data
 
328
        except Exception, msg:
 
329
            import traceback
 
330
            traceback.print_exc()
 
331
            QMessageBox.critical(self.parent(), APP_NAME,
 
332
                                 _(u"Error:")+"\n%s" % str(msg))
 
333
            return
 
334
        sumobj.data /= float(len(rows))
 
335
        sumobj.change_data_type(dtype=original_dtype)
 
336
        self.add_object(sumobj)
 
337
    
 
338
    def compute_product(self):
 
339
        rows = self._get_selected_rows()
 
340
        sumobj = self.PARAMCLASS()
 
341
        sumobj.title = "*".join(["%s%03d" % (self.PREFIX, row) for row in rows])
 
342
        try:
 
343
            for row in rows:
 
344
                obj = self.objects[row]
 
345
                if sumobj.data is None:
 
346
                    sumobj.copy_data_from(obj)
 
347
                else:
 
348
                    sumobj.data *= obj.data
 
349
        except Exception, msg:
 
350
            import traceback
 
351
            traceback.print_exc()
 
352
            QMessageBox.critical(self.parent(), APP_NAME,
 
353
                                 _(u"Error:")+"\n%s" % str(msg))
 
354
            return
 
355
        self.add_object(sumobj)
 
356
    
 
357
    def compute_difference(self):
 
358
        rows = self._get_selected_rows()
 
359
        diffobj = self.PARAMCLASS()
 
360
        diffobj.title = "-".join(["%s%03d" % (self.PREFIX, row)
 
361
                                  for row in rows])
 
362
        try:
 
363
            obj0, obj1 = self.objects[rows[0]], self.objects[rows[1]]
 
364
            diffobj.copy_data_from(obj0)
 
365
            diffobj.data = obj1.data-obj0.data
 
366
        except Exception, msg:
 
367
            import traceback
 
368
            traceback.print_exc()
 
369
            QMessageBox.critical(self.parent(), APP_NAME,
 
370
                                 _(u"Error:")+"\n%s" % str(msg))
 
371
            return
 
372
        self.add_object(diffobj)
 
373
    
 
374
    def compute_division(self):
 
375
        rows = self._get_selected_rows()
 
376
        diffobj = self.PARAMCLASS()
 
377
        diffobj.title = "/".join(["%s%03d" % (self.PREFIX, row)
 
378
                                  for row in rows])
 
379
        try:
 
380
            obj0, obj1 = self.objects[rows[0]], self.objects[rows[1]]
 
381
            diffobj.copy_data_from(obj0)
 
382
            diffobj.data = obj1.data/obj0.data
 
383
        except Exception, msg:
 
384
            import traceback
 
385
            traceback.print_exc()
 
386
            QMessageBox.critical(self.parent(), APP_NAME,
 
387
                                 _(u"Error:")+"\n%s" % str(msg))
 
388
            return
 
389
        self.add_object(diffobj)
 
390
                                     
 
391
    #------Data Processing
 
392
    def apply_11_func(self, obj, orig, func, param):
 
393
        if param is None:
 
394
            obj.data = func(orig.data)
 
395
        else:
 
396
            obj.data = func(orig.data, param)
 
397
    
 
398
    def compute_11(self, name, func, param=None, one_param_for_all=True,
 
399
                   suffix=None):
 
400
        if param is not None and one_param_for_all:
 
401
            if not param.edit(parent=self.parent()):
 
402
                return
 
403
        rows = self._get_selected_rows()
 
404
        for row in rows:
 
405
            if param is not None and not one_param_for_all:
 
406
                if not param.edit(parent=self.parent()):
 
407
                    return
 
408
            orig = self.objects[row]
 
409
            obj = self.PARAMCLASS()
 
410
            obj.title = "%s(%s%03d)" % (name, self.PREFIX, row)
 
411
            if suffix is not None:
 
412
                obj.title += "|"+suffix(param)
 
413
            obj.copy_data_from(orig)
 
414
            self.emit(SIGNAL("status_message(QString)"),
 
415
                      _("Computing:")+" "+obj.title)
 
416
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
 
417
            self.repaint()
 
418
            try:
 
419
                self.apply_11_func(obj, orig, func, param)
 
420
            except Exception, msg:
 
421
                import traceback
 
422
                traceback.print_exc()
 
423
                QMessageBox.critical(self.parent(), APP_NAME,
 
424
                                     _(u"Error:")+"\n%s" % str(msg))
 
425
                return
 
426
            finally:
 
427
                self.emit(SIGNAL("status_message(QString)"), "")
 
428
                QApplication.restoreOverrideCursor()
 
429
            self.add_object(obj)
 
430
        
 
431
class SignalFT(ObjectFT):
 
432
    PARAMCLASS = SignalParam
 
433
    PREFIX = "s"
 
434
    #------ObjectFT API
 
435
    def setup(self, toolbar):
 
436
        super(SignalFT, self).setup(toolbar)
 
437
        
 
438
        # File actions
 
439
        new_action = create_action(self, _("New signal..."),
 
440
                                   icon=get_icon('filenew.png'),
 
441
                                   tip=_("Create a new signal"),
 
442
                                   triggered=self.new_signal)
 
443
        open_action = create_action(self, _("Open signal..."),
 
444
                                    icon=get_icon('fileopen.png'),
 
445
                                    tip=_("Open a signal"),
 
446
                                    triggered=self.open_signal)
 
447
        save_action = create_action(self, _("Save signal..."),
 
448
                                    icon=get_icon('filesave.png'),
 
449
                                    tip=_("Save selected signal"),
 
450
                                    triggered=self.save_signal)
 
451
        self.actlist_1more += [save_action]
 
452
        self.file_actions = [new_action, open_action, save_action]
 
453
        
 
454
        # Processing actions
 
455
        gaussian_action = create_action(self, _("Gaussian filter"),
 
456
                                        triggered=self.compute_gaussian)
 
457
        wiener_action = create_action(self, _("Wiener filter"),
 
458
                                      triggered=self.compute_wiener)
 
459
        fft_action = create_action(self, _("FFT"),
 
460
                                   tip=_("Warning: only real part is plotted"),
 
461
                                   triggered=self.compute_fft)
 
462
        ifft_action = create_action(self, _("Inverse FFT"),
 
463
                                   tip=_("Warning: only real part is plotted"),
 
464
                                    triggered=self.compute_ifft)
 
465
        self.actlist_1more += [gaussian_action, wiener_action,
 
466
                               fft_action, ifft_action]
 
467
        self.processing_actions = [gaussian_action, wiener_action, fft_action,
 
468
                                   ifft_action]
 
469
                                   
 
470
        add_actions(toolbar, [new_action, open_action, save_action])
 
471
 
 
472
    def make_item(self, row):
 
473
        signal = self.objects[row]
 
474
        x, y = signal.xydata
 
475
        item = make.mcurve(x, y.real, label=signal.title)
 
476
        self.items[row] = item
 
477
        return item
 
478
        
 
479
    def update_item(self, row):
 
480
        signal = self.objects[row]
 
481
        x, y = signal.xydata
 
482
        item = self.items[row]
 
483
        item.set_data(x, y.real)
 
484
        item.curveparam.label = signal.title
 
485
        
 
486
    #------Signal Processing
 
487
    def apply_11_func(self, obj, orig, func, param):
 
488
        xor, yor = orig.xydata
 
489
        if param is None:
 
490
            obj.xydata = func(xor, yor)
 
491
        else:
 
492
            obj.xydata = func(xor, yor, param)
 
493
    
 
494
    def compute_wiener(self):
 
495
        import scipy.signal as sps
 
496
        def func(x, y):
 
497
            return x, sps.wiener(y)
 
498
        self.compute_11("WienerFilter", func)
 
499
    
 
500
    def compute_gaussian(self):
 
501
        class GaussianParam(DataSet):
 
502
            sigma = FloatItem(u"σ", default=1.)
 
503
        param = GaussianParam(_("Gaussian filter"))
 
504
        import scipy.ndimage as spi
 
505
        def func(x, y, p):
 
506
            return x, spi.gaussian_filter1d(y, p.sigma)
 
507
        self.compute_11("GaussianFilter", func, param,
 
508
                        suffix=lambda p: u"σ=%.3f pixels" % p.sigma)
 
509
                         
 
510
    def compute_fft(self):
 
511
        self.compute_11("FFT", xy_fft)
 
512
                         
 
513
    def compute_ifft(self):
 
514
        self.compute_11("iFFT", xy_ifft)
 
515
                            
 
516
    #------I/O
 
517
    def new_signal(self):
 
518
        """Create a new signal"""
 
519
        signalnew = SignalParamNew(title=_("Create a new signal"))
 
520
        rows = self._get_selected_rows()
 
521
        if rows:
 
522
            signalnew.size = len(self.objects[rows[-1]].data)
 
523
        signalnew.title = "%s %d" % (signalnew.title, self.number+1)
 
524
        if not signalnew.edit(parent=self.parent()):
 
525
            return
 
526
        self.number += 1
 
527
        signal = SignalParam()
 
528
        signal.title = signalnew.title
 
529
        xarr = np.linspace(signalnew.xmin, signalnew.xmax, signalnew.size)
 
530
        if signalnew.type == 'zeros':
 
531
            signal.xydata = np.vstack((xarr, np.zeros(signalnew.size)))
 
532
        elif signalnew.type == 'rand':
 
533
            signal.xydata = np.vstack((xarr, np.random.rand(signalnew.size)-.5))
 
534
        elif signalnew.type == 'gauss':
 
535
            class GaussParam(DataSet):
 
536
                a = FloatItem("Norm", default=1.)
 
537
                x0 = FloatItem("X0", default=0.0)
 
538
                sigma = FloatItem(u"σ", default=5.)
 
539
            param = GaussParam(_("New gaussian function"))
 
540
            if not param.edit(parent=self.parent()):
 
541
                return
 
542
            ygauss = param.a*np.exp(-.5*((xarr-param.x0)/param.sigma)**2)
 
543
            signal.xydata = np.vstack((xarr, ygauss))
 
544
        self.add_object(signal)
 
545
    
 
546
    def open_signal(self):
 
547
        """Open signal file"""
 
548
        saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
 
549
        sys.stdout = None
 
550
        filters = '%s (*.txt *.csv)\n%s (*.npy)'\
 
551
                  % (_(u"Text files"), _(u"NumPy arrays"))
 
552
        filenames = QFileDialog.getOpenFileNames(self.parent(), _("Open"),
 
553
                                               self.directory, filters)
 
554
        sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
 
555
        filenames = list(filenames)
 
556
        for filename in filenames:
 
557
            filename = unicode(filename)
 
558
            self.directory = osp.dirname(filename)
 
559
            signal = SignalParam()
 
560
            signal.title = filename
 
561
            try:
 
562
                if osp.splitext(filename)[1] == ".npy":
 
563
                    xydata =np.load(filename)
 
564
                else:
 
565
                    for delimiter in ('\t', ',', ' ', ';'):
 
566
                        try:
 
567
                            xydata = np.loadtxt(filename, delimiter=delimiter)
 
568
                            break
 
569
                        except ValueError:
 
570
                            continue
 
571
                    else:
 
572
                        raise
 
573
            except Exception, msg:
 
574
                import traceback
 
575
                traceback.print_exc()
 
576
                QMessageBox.critical(self.parent(), APP_NAME,
 
577
                     (_(u"%s could not be opened:") % osp.basename(filename))+\
 
578
                     "\n"+str(msg))
 
579
                return
 
580
            if len(xydata.shape) == 1:
 
581
                xydata = np.vstack( (np.arange(xydata.size), xydata) )
 
582
            elif len(xydata.shape) == 2:
 
583
                rows, cols = xydata.shape
 
584
                if cols == 2 and rows > 2:
 
585
                    xydata = xydata.T
 
586
            signal.xydata = xydata
 
587
            self.add_object(signal)
 
588
            
 
589
    def save_signal(self):
 
590
        """Save selected signal"""
 
591
        rows = self._get_selected_rows()
 
592
        for row in rows:
 
593
            filename = QFileDialog.getSaveFileName(self, _("Save as"), 
 
594
                                   self.directory, _(u"CSV files")+" (*.csv)")
 
595
            if not filename:
 
596
                return
 
597
            filename = unicode(filename)
 
598
            self.directory = osp.dirname(filename)
 
599
            obj = self.objects[row]
 
600
            try:
 
601
                np.savetxt(filename, obj.xydata, delimiter=',')
 
602
            except Exception, msg:
 
603
                import traceback
 
604
                traceback.print_exc()
 
605
                QMessageBox.critical(self.parent(), APP_NAME,
 
606
                     (_(u"%s could not be written:") % osp.basename(filename))+\
 
607
                     "\n"+str(msg))
 
608
                return
 
609
 
 
610
class ImageFT(ObjectFT):
 
611
    PARAMCLASS = ImageParam
 
612
    PREFIX = "i"
 
613
    #------ObjectFT API
 
614
    def setup(self, toolbar):
 
615
        super(ImageFT, self).setup(toolbar)
 
616
        
 
617
        # File actions
 
618
        new_action = create_action(self, _("New image..."),
 
619
                                   icon=get_icon('filenew.png'),
 
620
                                   tip=_("Create a new image"),
 
621
                                   triggered=self.new_image)
 
622
        open_action = create_action(self, _("Open image..."),
 
623
                                    icon=get_icon('fileopen.png'),
 
624
                                    tip=_("Open an image"),
 
625
                                    triggered=self.open_image)
 
626
        save_action = create_action(self, _("Save image..."),
 
627
                                    icon=get_icon('filesave.png'),
 
628
                                    tip=_("Save selected image"),
 
629
                                    triggered=self.save_image)
 
630
        self.actlist_1more += [save_action]
 
631
        self.file_actions = [new_action, open_action, save_action]
 
632
 
 
633
        # Operation actions
 
634
        rotate_menu = QMenu(_("Rotation"), self)
 
635
        hflip_action = create_action(self, _("Flip horizontally"),
 
636
                                     triggered=self.flip_horizontally)
 
637
        vflip_action = create_action(self, _("Flip vertically"),
 
638
                                     triggered=self.flip_vertically)
 
639
        rot90_action = create_action(self, _("Rotate 90° right"),
 
640
                                     triggered=self.rotate_270)
 
641
        rot270_action = create_action(self, _("Rotate 90° left"),
 
642
                                      triggered=self.rotate_90)
 
643
        rotate_action = create_action(self, _("Rotate arbitrarily..."),
 
644
                                      triggered=self.rotate_arbitrarily)
 
645
        resize_action = create_action(self, _("Resize"),
 
646
                                      triggered=self.resize_image)
 
647
        crop_action = create_action(self, _("Crop"), triggered=self.crop_image)
 
648
        flatfield_action = create_action(self, _("Flat-field correction"),
 
649
                                         triggered=self.flat_field_correction)
 
650
        self.actlist_1 += [resize_action]
 
651
        self.actlist_2 += [flatfield_action]
 
652
        self.actlist_1more += [crop_action, hflip_action, vflip_action,
 
653
                               rot90_action, rot270_action, rotate_action]
 
654
        add_actions(rotate_menu, [hflip_action, vflip_action,
 
655
                                  rot90_action, rot270_action, rotate_action])
 
656
        self.operation_actions += [None, rotate_menu, None,
 
657
                                   resize_action, crop_action,
 
658
                                   None, flatfield_action]
 
659
        
 
660
        # Processing actions
 
661
        threshold_action = create_action(self, _("Thresholding"),
 
662
                                         triggered=self.compute_threshold)
 
663
        clip_action = create_action(self, _("Clipping"),
 
664
                                    triggered=self.compute_clip)
 
665
        gaussian_action = create_action(self, _("Gaussian filter"),
 
666
                                        triggered=self.compute_gaussian)
 
667
        wiener_action = create_action(self, _("Wiener filter"),
 
668
                                      triggered=self.compute_wiener)
 
669
        fft_action = create_action(self, _("FFT"),
 
670
                                   tip=_("Warning: only real part is plotted"),
 
671
                                   triggered=self.compute_fft)
 
672
        ifft_action = create_action(self, _("Inverse FFT"),
 
673
                                   tip=_("Warning: only real part is plotted"),
 
674
                                    triggered=self.compute_ifft)
 
675
        self.actlist_1more += [threshold_action, clip_action,
 
676
                               gaussian_action, wiener_action,
 
677
                               fft_action, ifft_action]
 
678
        self.processing_actions = [threshold_action, clip_action, None,
 
679
                                   gaussian_action, wiener_action, fft_action,
 
680
                                   ifft_action]
 
681
                                   
 
682
        add_actions(toolbar, [new_action, open_action, save_action])
 
683
        
 
684
    def make_item(self, row):
 
685
        image = self.objects[row]
 
686
        item = make.image(image.data.real, title=image.title, colormap='gray',
 
687
                          eliminate_outliers=2.)
 
688
        self.items[row] = item
 
689
        return item
 
690
        
 
691
    def update_item(self, row):
 
692
        image = self.objects[row]
 
693
        item = self.items[row]
 
694
        lut_range = [item.min, item.max]
 
695
        item.set_data(image.data.real, lut_range=lut_range)
 
696
        item.imageparam.label = image.title
 
697
        item.plot().update_colormap_axis(item)
 
698
        
 
699
    #------Image operations
 
700
    def rotate_arbitrarily(self):
 
701
        boundaries = ('constant', 'nearest', 'reflect', 'wrap')
 
702
        prop = ValueProp(False)
 
703
        class RotateParam(DataSet):
 
704
            angle = FloatItem(u"%s (°)" % _(u"Angle"))
 
705
            mode = ChoiceItem(_(u"Mode"), zip(boundaries, boundaries),
 
706
                              default=boundaries[0])
 
707
            cval = FloatItem(_("cval"), default=0.,
 
708
                             help=_(u"Value used for points outside the "
 
709
                                    u"boundaries of the input if mode is "
 
710
                                    u"'constant'"))
 
711
            prefilter = BoolItem(_(u"Prefilter the input image"),
 
712
                                 default=True).set_prop("display", store=prop)
 
713
            order = IntItem(_(u"Order"), default=3, min=0, max=5,
 
714
                            help=_("Spline interpolation order")
 
715
                            ).set_prop("display", active=prop)
 
716
        param = RotateParam(_("Rotation"))
 
717
        import scipy.ndimage as spi
 
718
        self.compute_11("Rotate",
 
719
                        lambda x, p:
 
720
                        spi.rotate(x, p.angle, order=p.order, mode=p.mode,
 
721
                                   cval=p.cval, prefilter=p.prefilter),
 
722
                        param, suffix=lambda p: u"α=%.3f°, mode='%s'"\
 
723
                                                % (p.angle, p.mode))
 
724
    
 
725
    def rotate_90(self):
 
726
        self.compute_11("Rotate90", lambda x: np.rot90(x))
 
727
        
 
728
    def rotate_270(self):
 
729
        self.compute_11("Rotate270", lambda x: np.rot90(x, 3))
 
730
        
 
731
    def flip_horizontally(self):
 
732
        self.compute_11("HFlip", lambda x: np.fliplr(x))
 
733
        
 
734
    def flip_vertically(self):
 
735
        self.compute_11("VFlip", lambda x: np.flipud(x))
 
736
        
 
737
    def resize_image(self):
 
738
        rows = self._get_selected_rows()
 
739
        obj = self.objects[rows[0]]
 
740
        original_size = obj.data.shape[1], obj.data.shape[0]
 
741
        from guiqwt.resizedialog import ResizeDialog
 
742
        dlg = ResizeDialog(self.plot, new_size=original_size,
 
743
                           old_size=original_size,
 
744
                           text=_("Destination size:"))
 
745
        if not dlg.exec_():
 
746
            return
 
747
        boundaries = ('constant', 'nearest', 'reflect', 'wrap')
 
748
        prop = ValueProp(False)
 
749
        class ResizeParam(DataSet):
 
750
            zoom = FloatItem(_(u"Zoom"), default=dlg.get_zoom())
 
751
            mode = ChoiceItem(_(u"Mode"), zip(boundaries, boundaries),
 
752
                              default=boundaries[0])
 
753
            cval = FloatItem(_("cval"), default=0.,
 
754
                             help=_(u"Value used for points outside the "
 
755
                                    u"boundaries of the input if mode is "
 
756
                                    u"'constant'"))
 
757
            prefilter = BoolItem(_(u"Prefilter the input image"),
 
758
                                 default=True).set_prop("display", store=prop)
 
759
            order = IntItem(_(u"Order"), default=3, min=0, max=5,
 
760
                            help=_("Spline interpolation order")
 
761
                            ).set_prop("display", active=prop)
 
762
        param = ResizeParam(_("Resize"))
 
763
        import scipy.ndimage as spi
 
764
        self.compute_11("Zoom", lambda x, p:
 
765
                        spi.interpolation.zoom(x, p.zoom, order=p.order,
 
766
                                               mode=p.mode, cval=p.cval,
 
767
                                               prefilter=p.prefilter),
 
768
                        param, suffix=lambda p: u"zoom=%.3f" % p.zoom)
 
769
                        
 
770
    def crop_image(self):
 
771
        class CropParam(DataSet):
 
772
            row1 = IntItem(_("First row index"), default=0, min=-1)
 
773
            row2 = IntItem(_("Last row index"), default=-1, min=-1)
 
774
            col1 = IntItem(_("First column index"), default=0, min=-1)
 
775
            col2 = IntItem(_("Last column index"), default=-1, min=-1)
 
776
        param = CropParam(_("Crop"))
 
777
        self.compute_11("Crop", lambda x, p:
 
778
                        x.copy()[p.row1:p.row2, p.col1:p.col2],
 
779
                        param, suffix=lambda p: u"roi=%d:%d,%d:%d" 
 
780
                        % (p.row1, p.row2, p.col1, p.col2))
 
781
        
 
782
    def flat_field_correction(self):
 
783
        rows = self._get_selected_rows()
 
784
        robj = self.PARAMCLASS()
 
785
        robj.title = "FlatField("+(','.join(["%s%03d" % (self.PREFIX, row)
 
786
                                             for row in rows]))+")"
 
787
        try:
 
788
            robj.data = flatfield(self.objects[rows[0]].data,
 
789
                                  self.objects[rows[1]].data)
 
790
        except Exception, msg:
 
791
            import traceback
 
792
            traceback.print_exc()
 
793
            QMessageBox.critical(self.parent(), APP_NAME,
 
794
                                 _(u"Error:")+"\n%s" % str(msg))
 
795
            return
 
796
        self.add_object(robj)
 
797
        
 
798
    #------Image Processing
 
799
    def compute_threshold(self):
 
800
        class ThresholdParam(DataSet):
 
801
            value = FloatItem(_(u"Threshold"))
 
802
        self.compute_11("Threshold", lambda x, p: np.clip(x, p.value, x.max()),
 
803
                        ThresholdParam(_("Thresholding")),
 
804
                        suffix=lambda p: u"min=%s lsb" % p.value)
 
805
                        
 
806
    def compute_clip(self):
 
807
        class ClipParam(DataSet):
 
808
            value = FloatItem(_(u"Clipping value"))
 
809
        self.compute_11("Clip", lambda x, p: np.clip(x, x.min(), p.value),
 
810
                        ClipParam(_("Clipping")),
 
811
                        suffix=lambda p: u"max=%s lsb" % p.value)
 
812
                        
 
813
    def compute_wiener(self):
 
814
        import scipy.signal as sps
 
815
        self.compute_11("WienerFilter", sps.wiener)
 
816
    
 
817
    def compute_gaussian(self):
 
818
        class GaussianParam(DataSet):
 
819
            sigma = FloatItem(u"σ", default=1.)
 
820
        param = GaussianParam(_("Gaussian filter"))
 
821
        import scipy.ndimage as spi
 
822
        self.compute_11("GaussianFilter",
 
823
                        lambda x, p: spi.gaussian_filter(x, p.sigma), param,
 
824
                        suffix=lambda p: u"σ=%.3f pixels" % p.sigma)
 
825
                         
 
826
    def compute_fft(self):
 
827
        self.compute_11("FFT", np.fft.fft2)
 
828
                         
 
829
    def compute_ifft(self):
 
830
        self.compute_11("iFFT", np.fft.ifft2)
 
831
                            
 
832
    #------I/O
 
833
    def new_image(self):
 
834
        """Create a new image"""
 
835
        imagenew = ImageParamNew(title=_("Create a new image"))
 
836
        rows = self._get_selected_rows()
 
837
        if rows:
 
838
            data = self.objects[rows[-1]].data
 
839
            imagenew.width = data.shape[1]
 
840
            imagenew.height = data.shape[0]
 
841
        imagenew.title = "%s %d" % (imagenew.title, self.number+1)
 
842
        if not imagenew.edit(parent=self.parent()):
 
843
            return
 
844
        self.number += 1
 
845
        image = ImageParam()
 
846
        image.title = imagenew.title
 
847
        shape = (imagenew.height, imagenew.width)
 
848
        dtype = imagenew.dtype
 
849
        if imagenew.type == 'zeros':
 
850
            image.data = np.zeros(shape, dtype=dtype)
 
851
        elif imagenew.type == 'empty':
 
852
            image.data = np.empty(shape, dtype=dtype)
 
853
        elif imagenew.type == 'rand':
 
854
            data = np.random.rand(*shape)
 
855
            from guiqwt.io import set_dynamic_range_from_dtype
 
856
            image.data = set_dynamic_range_from_dtype(data, dtype)
 
857
        self.add_object(image)
 
858
    
 
859
    def open_image(self):
 
860
        """Open image file"""
 
861
        saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
 
862
        sys.stdout = None
 
863
        filenames = QFileDialog.getOpenFileNames(self.parent(), _("Open"),
 
864
                                           self.directory, IMAGE_LOAD_FILTERS)
 
865
        sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
 
866
        filenames = list(filenames)
 
867
        for filename in filenames:
 
868
            filename = unicode(filename)
 
869
            self.directory = osp.dirname(filename)
 
870
            image = ImageParam()
 
871
            image.title = filename
 
872
            try:
 
873
                image.data = imagefile_to_array(filename, to_grayscale=True)
 
874
                if osp.splitext(filename)[1].lower() == ".dcm":
 
875
                    import dicom
 
876
                    dcm = dicom.read_file(filename, stop_before_pixels=True)
 
877
                    image.metadata = {}
 
878
                    for attr_str in dir(dcm):
 
879
                        if attr_str != 'GroupLength':
 
880
                            image.metadata[attr_str] = getattr(dcm, attr_str)
 
881
            except Exception, msg:
 
882
                import traceback
 
883
                traceback.print_exc()
 
884
                QMessageBox.critical(self.parent(), APP_NAME,
 
885
                     (_(u"%s could not be opened:") % osp.basename(filename))+\
 
886
                     "\n"+str(msg))
 
887
                return
 
888
            self.add_object(image)
 
889
            
 
890
    def save_image(self):
 
891
        """Save selected image"""
 
892
        rows = self._get_selected_rows()
 
893
        for row in rows:
 
894
            filename = QFileDialog.getSaveFileName(self, _("Save as"), 
 
895
                                           self.directory, IMAGE_SAVE_FILTERS)
 
896
            if not filename:
 
897
                return
 
898
            filename = unicode(filename)
 
899
            self.directory = osp.dirname(filename)
 
900
            obj = self.objects[row]
 
901
            try:
 
902
                array_to_imagefile(obj.data, filename)
 
903
            except Exception, msg:
 
904
                import traceback
 
905
                traceback.print_exc()
 
906
                QMessageBox.critical(self.parent(), APP_NAME,
 
907
                     (_(u"%s could not be written:") % osp.basename(filename))+\
 
908
                     "\n"+str(msg))
 
909
                return
 
910
        
 
911
 
 
912
class DockablePlotWidget(DockableWidget):
 
913
    LOCATION = Qt.RightDockWidgetArea
 
914
    def __init__(self, parent, plotwidgetclass, toolbar):
 
915
        super(DockablePlotWidget, self).__init__(parent)
 
916
        self.toolbar = toolbar
 
917
        layout = QVBoxLayout()
 
918
        self.plotwidget = plotwidgetclass()
 
919
        layout.addWidget(self.plotwidget)
 
920
        self.setLayout(layout)
 
921
        self.setup()
 
922
        
 
923
    def get_plot(self):
 
924
        return self.plotwidget.plot
 
925
        
 
926
    def setup(self):
 
927
        title = unicode(self.toolbar.windowTitle())
 
928
        self.plotwidget.add_toolbar(self.toolbar, title)
 
929
        if isinstance(self.plotwidget, ImageWidget):
 
930
            self.plotwidget.register_all_image_tools()
 
931
        else:
 
932
            self.plotwidget.register_all_curve_tools()
 
933
        
 
934
    #------DockableWidget API
 
935
    def visibility_changed(self, enable):
 
936
        """DockWidget visibility has changed"""
 
937
        super(DockablePlotWidget, self).visibility_changed(enable)
 
938
        self.toolbar.setVisible(enable)
 
939
            
 
940
 
 
941
class DockableTabWidget(QTabWidget, DockableWidgetMixin):
 
942
    LOCATION = Qt.LeftDockWidgetArea
 
943
    def __init__(self, parent):
 
944
        QTabWidget.__init__(self, parent)
 
945
        DockableWidgetMixin.__init__(self, parent)
 
946
 
 
947
 
 
948
try:
 
949
    from spyderlib.widgets.internalshell import InternalShell
 
950
 
 
951
    class DockableConsole(InternalShell, DockableWidgetMixin):
 
952
        LOCATION = Qt.BottomDockWidgetArea
 
953
        def __init__(self, parent, namespace, message, commands=[]):
 
954
            InternalShell.__init__(self, namespace=namespace, message=message,
 
955
                                   commands=commands)
 
956
            DockableWidgetMixin.__init__(self, parent)
 
957
            self.setup()
 
958
            
 
959
        def setup(self):
 
960
            font = QFont("Courier new")
 
961
            font.setPointSize(10)
 
962
            self.set_font(font)
 
963
            self.set_codecompletion_auto(True)
 
964
            self.set_calltips(True)
 
965
            self.setup_calltips(size=600, font=font)
 
966
            self.setup_completion(size=(300, 180), font=font)
 
967
            
 
968
except ImportError:
 
969
    DockableConsole = None
 
970
 
 
971
 
 
972
class SiftProxy(object):
 
973
    def __init__(self, win):
 
974
        self.win = win
 
975
        self.s = self.win.signalft.objects
 
976
        self.i = self.win.signalft.objects
 
977
        
 
978
 
 
979
class MainWindow(QMainWindow):
 
980
    def __init__(self):
 
981
        QMainWindow.__init__(self)
 
982
 
 
983
        self.setWindowIcon(get_icon('sift.svg'))
 
984
        self.setWindowTitle(APP_NAME)
 
985
                
 
986
        # Welcome message in statusbar:
 
987
        status = self.statusBar()
 
988
        status.showMessage(_("Welcome to %s!") % APP_NAME, 5000)
 
989
 
 
990
        self.signal_toolbar = self.addToolBar(_("Signal Processing Toolbar"))
 
991
        self.image_toolbar = self.addToolBar(_("Image Processing Toolbar"))
 
992
 
 
993
        # Signals
 
994
        curveplot_toolbar = self.addToolBar(_("Curve Plotting Toolbar"))
 
995
        self.curvewidget = DockablePlotWidget(self, CurveWidget,
 
996
                                              curveplot_toolbar)
 
997
        curveplot = self.curvewidget.get_plot()
 
998
        curveplot.add_item(make.legend("TR"))
 
999
        self.signalft = SignalFT(self, plot=curveplot)
 
1000
        self.signalft.setup(self.signal_toolbar)
 
1001
        
 
1002
        # Images
 
1003
        imagevis_toolbar = self.addToolBar(_("Image Visualization Toolbar"))
 
1004
        self.imagewidget = DockablePlotWidget(self, ImageWidget,
 
1005
                                              imagevis_toolbar)
 
1006
        self.imageft = ImageFT(self, self.imagewidget.get_plot())
 
1007
        self.imageft.setup(self.image_toolbar)
 
1008
 
 
1009
        for objectft in (self.signalft, self.imageft):
 
1010
            self.connect(objectft, SIGNAL("status_message(QString)"),
 
1011
                         status.showMessage)
 
1012
        
 
1013
        # Main window widgets
 
1014
        self.tabwidget = DockableTabWidget(self)
 
1015
        self.tabwidget.setMaximumWidth(500)
 
1016
        self.tabwidget.addTab(self.signalft, get_icon('curve.png'),
 
1017
                              _("Signals"))
 
1018
        self.tabwidget.addTab(self.imageft, get_icon('image.png'),
 
1019
                              _("Images"))
 
1020
        self.add_dockwidget(self.tabwidget, _(u"Main panel"))
 
1021
#        self.setCentralWidget(self.tabwidget)
 
1022
        self.curve_dock = self.add_dockwidget(self.curvewidget,
 
1023
                                              title=_("Curve plotting panel"))
 
1024
        self.image_dock = self.add_dockwidget(self.imagewidget,
 
1025
                                          title=_("Image visualization panel"))
 
1026
        self.tabifyDockWidget(self.curve_dock, self.image_dock)
 
1027
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
 
1028
                     self.tab_index_changed)
 
1029
        self.connect(self.signalft, SIGNAL('object_added()'),
 
1030
                     lambda: self.tabwidget.setCurrentIndex(0))
 
1031
        self.connect(self.imageft, SIGNAL('object_added()'),
 
1032
                     lambda: self.tabwidget.setCurrentIndex(1))
 
1033
        
 
1034
        # File menu
 
1035
        self.quit_action = create_action(self, _("Quit"), shortcut="Ctrl+Q",
 
1036
                                    icon=get_std_icon("DialogCloseButton"),
 
1037
                                    tip=_("Quit application"),
 
1038
                                    triggered=self.close)
 
1039
        self.file_menu = self.menuBar().addMenu(_("File"))
 
1040
        self.connect(self.file_menu, SIGNAL("aboutToShow()"),
 
1041
                     self.update_file_menu)
 
1042
        
 
1043
        # Edit menu
 
1044
        self.edit_menu = self.menuBar().addMenu(_("&Edit"))
 
1045
        self.connect(self.edit_menu, SIGNAL("aboutToShow()"),
 
1046
                     self.update_edit_menu)
 
1047
        
 
1048
        # Operation menu
 
1049
        self.operation_menu = self.menuBar().addMenu(_("Operations"))
 
1050
        self.connect(self.operation_menu, SIGNAL("aboutToShow()"),
 
1051
                     self.update_operation_menu)
 
1052
        
 
1053
        # Processing menu
 
1054
        self.proc_menu = self.menuBar().addMenu(_("Processing"))
 
1055
        self.connect(self.proc_menu, SIGNAL("aboutToShow()"),
 
1056
                     self.update_proc_menu)
 
1057
        
 
1058
        # View menu
 
1059
        self.view_menu = view_menu = self.createPopupMenu()
 
1060
        view_menu.setTitle(_(u"&View"))
 
1061
        self.menuBar().addMenu(view_menu)
 
1062
        
 
1063
        # Help menu
 
1064
        help_menu = self.menuBar().addMenu("?")
 
1065
        about_action = create_action(self, _("About..."),
 
1066
                                     icon=get_std_icon('MessageBoxInformation'),
 
1067
                                     triggered=self.about)
 
1068
        add_actions(help_menu, (about_action,))
 
1069
        
 
1070
        # Eventually add an internal console (requires 'spyderlib')
 
1071
        self.sift_proxy = SiftProxy(self)
 
1072
        if DockableConsole is None:
 
1073
            self.console = None
 
1074
        else:
 
1075
            import time, scipy.signal as sps, scipy.ndimage as spi
 
1076
            ns = {'sift': self.sift_proxy,
 
1077
                  'np': np, 'sps': sps, 'spi': spi,
 
1078
                  'os': os, 'sys': sys, 'osp': osp, 'time': time}
 
1079
            msg = "Example: sift.s[0] returns signal object #0\n"\
 
1080
                  "Modules imported at startup: "\
 
1081
                  "os, sys, os.path as osp, time, "\
 
1082
                  "numpy as np, scipy.signal as sps, scipy.ndimage as spi"
 
1083
            self.console = DockableConsole(self, namespace=ns, message=msg)
 
1084
            self.add_dockwidget(self.console, _(u"Console"))
 
1085
            self.connect(self.console.interpreter.widget_proxy,
 
1086
                         SIGNAL("new_prompt(QString)"),
 
1087
                         lambda txt: self.refresh_lists())
 
1088
        
 
1089
        # Update selection dependent actions
 
1090
        self.update_actions()
 
1091
        
 
1092
        # Show main window and raise the signal plot panel
 
1093
        self.show()
 
1094
        self.curve_dock.raise_()
 
1095
                
 
1096
    #------GUI refresh/setup
 
1097
    def add_dockwidget(self, child, title):
 
1098
        """Add QDockWidget and toggleViewAction"""
 
1099
        dockwidget, location = child.create_dockwidget(title)
 
1100
        self.addDockWidget(location, dockwidget)
 
1101
        return dockwidget
 
1102
        
 
1103
    def refresh_lists(self):
 
1104
        self.signalft.refresh_list()
 
1105
        self.imageft.refresh_list()
 
1106
        
 
1107
    def update_actions(self):
 
1108
        self.signalft.selection_changed()
 
1109
        self.imageft.selection_changed()
 
1110
        is_signal = self.tabwidget.currentWidget() is self.signalft
 
1111
        self.signal_toolbar.setVisible(is_signal)
 
1112
        self.image_toolbar.setVisible(not is_signal)
 
1113
        
 
1114
    def tab_index_changed(self, index):
 
1115
        dock = (self.curve_dock, self.image_dock)[index]
 
1116
        dock.raise_()
 
1117
        self.update_actions()
 
1118
 
 
1119
    def update_file_menu(self):        
 
1120
        self.file_menu.clear()
 
1121
        objectft = self.tabwidget.currentWidget()
 
1122
        actions = objectft.file_actions+[None, self.quit_action]
 
1123
        add_actions(self.file_menu, actions)
 
1124
 
 
1125
    def update_edit_menu(self):        
 
1126
        self.edit_menu.clear()
 
1127
        objectft = self.tabwidget.currentWidget()
 
1128
        add_actions(self.edit_menu, objectft.edit_actions)
 
1129
        
 
1130
    def update_operation_menu(self):
 
1131
        self.operation_menu.clear()
 
1132
        objectft = self.tabwidget.currentWidget()
 
1133
        add_actions(self.operation_menu, objectft.operation_actions)
 
1134
        
 
1135
    def update_proc_menu(self):
 
1136
        self.proc_menu.clear()
 
1137
        objectft = self.tabwidget.currentWidget()
 
1138
        add_actions(self.proc_menu, objectft.processing_actions)
 
1139
                    
 
1140
    #------?
 
1141
    def about(self):
 
1142
        QMessageBox.about( self, _("About ")+APP_NAME,
 
1143
              """<b>%s</b> v%s<br>%s<p>%s Pierre Raybaut
 
1144
              <br>Copyright &copy; 2010 CEA
 
1145
              <p>Python %s, Qt %s, PyQt %s %s %s""" % \
 
1146
              (APP_NAME, VERSION, APP_DESC, _("Developped by"),
 
1147
               platform.python_version(),
 
1148
               QT_VERSION_STR, PYQT_VERSION_STR, _("on"), platform.system()) )
 
1149
               
 
1150
    def closeEvent(self, event):
 
1151
        if self.console is not None:
 
1152
            self.console.exit_interpreter()
 
1153
        event.accept()
 
1154
 
 
1155
 
 
1156
def run():
 
1157
    from guidata import qapplication
 
1158
    app = qapplication()
 
1159
    window = MainWindow()
 
1160
    window.show()
 
1161
    app.exec_()
 
1162
 
 
1163
 
 
1164
if __name__ == '__main__':
 
1165
    run()
 
 
b'\\ No newline at end of file'