source: sasview/src/sas/perspectives/calculator/image_viewer.py @ 9df6a03

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 9df6a03 was 3db44fb, checked in by butler, 10 years ago

1) Fixed second issue that was caused by the recent cleanup of
DocumentationWindow?: loading html at anchor point for context help
(broken). In order to preserve the cleanup, the class was refactored to
take another parameter: html instruction string. This keeps it general
to accept not only the # anchor but alos queries of all sorts in the
future. Thus all modules using this class were also edited to match.

2) in process of editing the dozen or so instances did a bit of code
cleanup and pylint cleanup.

  • Property mode set to 100644
File size: 15.6 KB
Line 
1import os
2import sys
3import wx
4import numpy as np
5import matplotlib
6matplotlib.interactive(False)
7#Use the WxAgg back end. The Wx one takes too long to render
8matplotlib.use('WXAgg')
9from sas.guiframe.local_perspectives.plotting.SimplePlot import PlotFrame
10#import matplotlib.pyplot as plt
11import matplotlib.image as mpimg
12import matplotlib.colors as colors
13from sas.guiframe.events import StatusEvent
14from sas.perspectives.calculator.calculator_widgets import InputTextCtrl
15from sas.dataloader.data_info import Data2D
16from sas.dataloader.data_info import Detector
17from sas.dataloader.manipulations import reader2D_converter
18from sas.guiframe.documentation_window import DocumentationWindow
19
20_BOX_WIDTH = 60
21IS_WIN = True
22if sys.platform.count("win32") > 0:
23    _DIALOG_WIDTH = 400
24else:
25    _DIALOG_WIDTH = 480
26    IS_WIN = False
27
28class ImageView:
29    """
30    Open a file dialog to allow the user to select a given file.
31    Display the loaded data if available.
32    """
33    def __init__(self, parent=None):
34        """
35        Init
36        """
37        self.parent = parent
38
39    def load(self):
40        """
41        load image files
42        """
43        parent = self.parent
44        if parent == None:
45            location = os.getcwd()
46        else:
47            location = parent._default_save_location
48        path_list = self.choose_data_file(location=location)
49        if path_list == None:
50            return
51        if len(path_list) >= 0 and not(path_list[0]is None):
52            if parent != None:
53                parent._default_save_location = os.path.dirname(path_list[0])
54        err_msg = ''
55        for file_path in path_list:
56            basename = os.path.basename(file_path)
57            _, extension = os.path.splitext(basename)
58            try:
59                img = mpimg.imread(file_path)
60                is_png = extension.lower() == '.png'
61                plot_frame = ImageFrame(parent, -1, basename, img)
62                plot_frame.Show(False)
63                ax = plot_frame.plotpanel
64                if not is_png:
65                    ax.subplot.set_ylim(ax.subplot.get_ylim()[::-1])
66                ax.subplot.set_xlabel('x [pixel]')
67                ax.subplot.set_ylabel('y [pixel]')
68                ax.figure.subplots_adjust(left=0.15, bottom=0.1,
69                                          right=0.95, top=0.95)
70                plot_frame.SetTitle('Picture -- %s --' % basename)
71                plot_frame.Show(True)
72                if parent != None:
73                    parent.put_icon(plot_frame)
74            except:
75                print "parent", parent
76                err_msg += "Failed to load '%s'.\n" % basename
77        if err_msg:
78            if parent is not None:
79                wx.PostEvent(parent, StatusEvent(status=err_msg, info="error"))
80            else:
81                print err_msg
82
83    def choose_data_file(self, location=None):
84        """
85        Open a file dialog to allow loading a file
86        """
87        path = None
88        if location == None:
89            location = os.getcwd()
90        dlg = wx.FileDialog(self.parent, "Image Viewer: Choose a image file",
91                            location, "", "", style=wx.FD_OPEN | wx.FD_MULTIPLE)
92        if dlg.ShowModal() == wx.ID_OK:
93            path = dlg.GetPaths()
94        else:
95            return None
96        dlg.Destroy()
97        return path
98
99class ImageFrame(PlotFrame):
100    """
101    Frame for simple plot
102    """
103    def __init__(self, parent, id, title, image=None, scale='log_{10}',
104                 size=wx.Size(550, 470)):
105        """
106        comment
107        :Param data: image array got from imread() of matplotlib [narray]
108        :param parent: parent panel/container
109        """
110        # Initialize the Frame object
111        PlotFrame.__init__(self, parent, id, title, scale, size)
112        self.parent = parent
113        self.data = image
114        self.file_name = title
115
116        menu = wx.Menu()
117        id = wx.NewId()
118        item = wx.MenuItem(menu, id, "&Convert to Data")
119        menu.AppendItem(item)
120        wx.EVT_MENU(self, id, self.on_set_data)
121        self.menu_bar.Append(menu, "&Image")
122
123        menu_help = wx.Menu()
124        id = wx.NewId()
125        item = wx.MenuItem(menu_help, id, "&HowTo")
126        menu_help.AppendItem(item)
127        wx.EVT_MENU(self, id, self.on_help)
128        self.menu_bar.Append(menu_help, "&Help")
129
130        self.SetMenuBar(self.menu_bar)
131        self.im_show(image)
132
133    def on_set_data(self, event):
134        """
135        Rescale the x y range, make 2D data and send it to data explore
136        """
137        title = self.file_name
138        self.panel = SetDialog(parent=self, title=title, image=self.data)
139        self.panel.ShowModal()
140
141    def on_help(self, event):
142        """
143        Bring up Image Viewer Documentation from the image viewer window
144        whenever the help menu item "how to" is clicked. Calls
145        DocumentationWindow with the path of the location within the
146        documentation tree (after /doc/ ....".
147
148        :param evt: Triggers on clicking "how to" in help menu
149        """
150
151        _TreeLocation = "user/perspectives/calculator/image_viewer_help.html"
152        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
153                                          "Image Viewer Help")
154
155
156class SetDialog(wx.Dialog):
157    """
158    Dialog for Data Set
159    """
160    def __init__(self, parent, id= -1, title="Convert to Data", image=None,
161                 size=(_DIALOG_WIDTH, 270)):
162        wx.Dialog.__init__(self, parent, id, title, size)
163        # parent
164        self.parent = parent
165        self.base = parent.parent
166        self.title = title
167        self.image = np.array(image)
168        self.z_ctrl = None
169        self.xy_ctrls = []
170        self.is_png = self._get_is_png()
171        self._build_layout()
172        my_title = "Convert Image to Data - %s -" % self.title
173        self.SetTitle(my_title)
174        self.SetSize(size)
175
176    def _get_is_png(self):
177        """
178        Get if the image file is png
179        """
180        _, extension = os.path.splitext(self.title)
181        return extension.lower() == '.png'
182
183    def _build_layout(self):
184        """
185        Layout
186        """
187        vbox = wx.BoxSizer(wx.VERTICAL)
188        zbox = wx.BoxSizer(wx.HORIZONTAL)
189        xbox = wx.BoxSizer(wx.HORIZONTAL)
190        ybox = wx.BoxSizer(wx.HORIZONTAL)
191        btnbox = wx.BoxSizer(wx.VERTICAL)
192
193        sb_title = wx.StaticBox(self, -1, 'Transform Axes')
194        boxsizer = wx.StaticBoxSizer(sb_title, wx.VERTICAL)
195        z_title = wx.StaticText(self, -1, 'z values (range: 0 - 255) to:')
196        ztime_title = wx.StaticText(self, -1, 'z *')
197        x_title = wx.StaticText(self, -1, 'x values from pixel # to:')
198        xmin_title = wx.StaticText(self, -1, 'xmin:')
199        xmax_title = wx.StaticText(self, -1, 'xmax:')
200        y_title = wx.StaticText(self, -1, 'y values from pixel # to:')
201        ymin_title = wx.StaticText(self, -1, 'ymin: ')
202        ymax_title = wx.StaticText(self, -1, 'ymax:')
203        z_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH , 20),
204                                style=wx.TE_PROCESS_ENTER)
205
206        xmin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
207                                style=wx.TE_PROCESS_ENTER)
208        xmax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
209                                style=wx.TE_PROCESS_ENTER)
210        ymin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
211                                style=wx.TE_PROCESS_ENTER)
212        ymax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
213                                style=wx.TE_PROCESS_ENTER)
214        z_ctl.SetValue('1.0')
215        xmin_ctl.SetValue('-0.3')
216        xmax_ctl.SetValue('0.3')
217        ymin_ctl.SetValue('-0.3')
218        ymax_ctl.SetValue('0.3')
219        z_ctl.Bind(wx.EVT_TEXT, self._on_z_enter)
220        xmin_ctl.Bind(wx.EVT_TEXT, self._onparam)
221        xmax_ctl.Bind(wx.EVT_TEXT, self._onparam)
222        ymin_ctl.Bind(wx.EVT_TEXT, self._onparam)
223        ymax_ctl.Bind(wx.EVT_TEXT, self._onparam)
224        xbox.AddMany([(x_title , 0, wx.LEFT, 0),
225                      (xmin_title , 0, wx.LEFT, 10),
226                      (xmin_ctl , 0, wx.LEFT, 10),
227                      (xmax_title , 0, wx.LEFT, 10),
228                      (xmax_ctl , 0, wx.LEFT, 10)])
229        ybox.AddMany([(y_title , 0, wx.LEFT, 0),
230                      (ymin_title , 0, wx.LEFT, 10),
231                      (ymin_ctl , 0, wx.LEFT, 10),
232                      (ymax_title , 0, wx.LEFT, 10),
233                      (ymax_ctl , 0, wx.LEFT, 10)])
234        zbox.AddMany([(z_title , 0, wx.LEFT, 0),
235                      (ztime_title, 0, wx.LEFT, 10),
236                      (z_ctl , 0, wx.LEFT, 7),
237                      ])
238        msg = "The data rescaled will show up in the Data Explorer. \n"
239        msg += "*Note: Recommend to use an image with 8 bit Grey \n"
240        msg += "  scale (and with No. of pixels < 300 x 300).\n"
241        msg += "  Otherwise, z = 0.299R + 0.587G + 0.114B."
242        note_txt = wx.StaticText(self, -1, msg)
243        note_txt.SetForegroundColour("black")
244        hbox = wx.BoxSizer(wx.HORIZONTAL)
245        okButton = wx.Button(self, -1, 'OK')
246        okButton.Bind(wx.EVT_BUTTON, self.on_set)
247        cancelButton = wx.Button(self, -1, 'Cancel')
248        cancelButton.Bind(wx.EVT_BUTTON, self.OnClose)
249        btnbox.Add(okButton, 0, wx.LEFT | wx.BOTTOM, 5)
250        btnbox.Add(cancelButton, 0, wx.LEFT | wx.TOP, 5)
251        hbox.Add(note_txt, 0, wx.LEFT, 5)
252        hbox.Add(btnbox, 0, wx.LEFT, 15)
253        vbox.Add((10, 15))
254        boxsizer.Add(xbox, 1, wx.LEFT | wx.BOTTOM, 5)
255        boxsizer.Add(ybox, 1, wx.LEFT | wx.BOTTOM, 5)
256        boxsizer.Add(zbox, 1, wx.LEFT | wx.BOTTOM, 5)
257        vbox.Add(boxsizer, 0, wx.LEFT, 20)
258        vbox.Add(hbox, 0, wx.LEFT | wx.TOP, 15)
259        okButton.SetFocus()
260        # set sizer
261        self.SetSizer(vbox)
262        #pos = self.parent.GetPosition()
263        #self.SetPosition(pos)
264        self.z_ctrl = z_ctl
265        self.xy_ctrls = [[xmin_ctl, xmax_ctl], [ymin_ctl, ymax_ctl]]
266
267    def _onparamEnter(self, event=None):
268        """
269        By pass original txtcrl binding
270        """
271        pass
272
273    def _onparam(self, event=None):
274        """
275        Set to default
276        """
277        item = event.GetEventObject()
278        self._check_ctrls(item)
279
280    def _check_ctrls(self, item, is_button=False):
281        """
282        """
283        flag = True
284        item.SetBackgroundColour("white")
285        try:
286            val = float(item.GetValue())
287            if val < -10.0 or val > 10.0:
288                item.SetBackgroundColour("pink")
289                item.Refresh()
290                flag = False
291        except:
292            item.SetBackgroundColour("pink")
293            item.Refresh()
294            flag = False
295        if not flag and is_button:
296            err_msg = "The allowed range of the min and max values are \n"
297            err_msg += "between -10 and 10."
298            if self.base is not None:
299                wx.PostEvent(self.base, StatusEvent(status=err_msg,
300                                                    info="error"))
301            else:
302                print err_msg
303        return flag
304
305    def _on_z_enter(self, event=None):
306        """
307        On z factor enter
308        """
309        item = event.GetEventObject()
310        self._check_z_ctrl(item)
311
312    def _check_z_ctrl(self, item, is_button=False):
313        """
314        """
315        flag = True
316        item.SetBackgroundColour("white")
317        try:
318            val = float(item.GetValue())
319            if val <= 0:
320                item.SetBackgroundColour("pink")
321                item.Refresh()
322                flag = False
323        except:
324            item.SetBackgroundColour("pink")
325            item.Refresh()
326            flag = False
327        if not flag and is_button:
328            err_msg = "The z scale value should be larger than 0."
329            if self.base is not None:
330                wx.PostEvent(self.base, StatusEvent(status=err_msg,
331                                                    info="error"))
332            else:
333                print err_msg
334        return flag
335
336    def on_set(self, event):
337        """
338        Set image as data
339        """
340        event.Skip()
341        # Check the textctrl values
342        for item_list in self.xy_ctrls:
343            for item in item_list:
344                 if not self._check_ctrls(item, True):
345                     return
346        if not self._check_z_ctrl(self.z_ctrl, True):
347            return
348        try:
349            image = self.image
350            xmin = float(self.xy_ctrls[0][0].GetValue())
351            xmax = float(self.xy_ctrls[0][1].GetValue())
352            ymin = float(self.xy_ctrls[1][0].GetValue())
353            ymax = float(self.xy_ctrls[1][1].GetValue())
354            zscale = float(self.z_ctrl.GetValue())
355            self.convert_image(image, xmin, xmax, ymin, ymax, zscale)
356        except:
357            err_msg = "Error occurred while converting Image to Data."
358            if self.base is not None:
359                wx.PostEvent(self.base, StatusEvent(status=err_msg,
360                                                    info="error"))
361            else:
362                print err_msg
363
364        self.OnClose(event)
365
366    def convert_image(self, rgb, xmin, xmax, ymin, ymax, zscale):
367        """
368        Convert image to data2D
369        """
370        x_len = len(rgb[0])
371        y_len = len(rgb)
372        x_vals = np.linspace(xmin, xmax, num=x_len)
373        y_vals = np.linspace(ymin, ymax, num=y_len)
374        # Instantiate data object
375        output = Data2D()
376        output.filename = os.path.basename(self.title)
377        output.id = output.filename
378        detector = Detector()
379        detector.pixel_size.x = None
380        detector.pixel_size.y = None
381        # Store the sample to detector distance
382        detector.distance = None
383        output.detector.append(detector)
384        # Initiazed the output data object
385        output.data = zscale * self.rgb2gray(rgb)
386        output.err_data = np.zeros([x_len, y_len])
387        output.mask = np.ones([x_len, y_len], dtype=bool)
388        output.xbins = x_len
389        output.ybins = y_len
390        output.x_bins = x_vals
391        output.y_bins = y_vals
392        output.qx_data = np.array(x_vals)
393        output.qy_data = np.array(y_vals)
394        output.xmin = xmin
395        output.xmax = xmax
396        output.ymin = ymin
397        output.ymax = ymax
398        output.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
399        output.yaxis('\\rm{Q_{y}}', '\AA^{-1}')
400        # Store loading process information
401        output.meta_data['loader'] = self.title.split('.')[-1] + "Reader"
402        output.is_data = True
403        output = reader2D_converter(output)
404        if self.base != None:
405            data = self.base.create_gui_data(output, self.title)
406            self.base.add_data({data.id:data})
407
408    def rgb2gray(self, rgb):
409        """
410        RGB to Grey
411        """
412        if self.is_png:
413            # png image limits: 0 to 1, others 0 to 255
414            #factor = 255.0
415            rgb = rgb[::-1]
416        if rgb.ndim == 2:
417            grey = np.rollaxis(rgb, axis=0)
418        else:
419            red, green, blue = np.rollaxis(rgb[..., :3], axis= -1)
420            grey = 0.299 * red + 0.587 * green + 0.114 * blue
421        max_i = rgb.max()
422        factor = 255.0 / max_i
423        grey *= factor
424        return np.array(grey)
425
426    def OnClose(self, event):
427        """
428        Close event
429        """
430        # clear event
431        event.Skip()
432        self.Destroy()
433
434if __name__ == "__main__":
435    app = wx.App()
436    ImageView(None).load()
437    app.MainLoop()
438
Note: See TracBrowser for help on using the repository browser.