source: sasview/src/sas/sasgui/perspectives/calculator/image_viewer.py @ ff2b961

Last change on this file since ff2b961 was 412e9e8b, checked in by butler, 7 years ago

Fix viewer documentation

Replaced the three images for the image_viewer_help.rst with new ones
that reflect the latest features. Also replaced the bmp with png.
Finaly added a comment in the image_viewer.py code noting the close
error generated by PIL (for any non png images). This should complete
the work on this branch.

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