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

ticket-1249
Last change on this file since 34f23c8 was 34f23c8, checked in by Paul Kienzle <pkienzle@…>, 3 months ago

py3/wx4 compatibility changes for gui. Refs #1249

  • Property mode set to 100644
File size: 16.2 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="|".join((
98            "Images (*.bmp;*.gif;*jpeg,*jpg;*.png;*tif;*.tiff)"
99                "|*bmp;*.gif;*.jpg;*.jpeg;*png;*.png;*.tif;*.tiff",
100            "Bitmap (*.bmp)|*.bmp",
101            "GIF (*.gif)|*.gif",
102            "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg",
103            "PNG (*.png)|*.png",
104            "TIFF (*.tif;*.tiff)|*.tif;*tiff",
105            "All Files (*.*)|*.*",
106            ))
107
108        dlg = wx.FileDialog(self.parent, "Image Viewer: Choose an image file",
109                            location, "", wildcard, style=wx.FD_OPEN
110                            | wx.FD_MULTIPLE)
111        if dlg.ShowModal() == wx.ID_OK:
112            path = dlg.GetPaths()
113        else:
114            return None
115        dlg.Destroy()
116        return path
117
118class ImageFrame(PlotFrame):
119    """
120    Frame for simple plot
121    """
122    def __init__(self, parent, id, title, image=None, scale='log_{10}',
123                 size=wx.Size(550, 470)):
124        """
125        comment
126        :Param data: image array got from imread() of matplotlib [narray]
127        :param parent: parent panel/container
128        """
129        # Initialize the Frame object
130        PlotFrame.__init__(self, parent, id, title, scale, size,
131            show_menu_icons=False)
132        self.parent = parent
133        self.data = image
134        self.file_name = title
135
136        menu = wx.Menu()
137        id = wx.NewId()
138        item = wx.MenuItem(menu, id, "&Convert to Data")
139        menu.AppendItem(item)
140        wx.EVT_MENU(self, id, self.on_set_data)
141        self.menu_bar.Append(menu, "&Image")
142
143        menu_help = wx.Menu()
144        id = wx.NewId()
145        item = wx.MenuItem(menu_help, id, "&HowTo")
146        menu_help.AppendItem(item)
147        wx.EVT_MENU(self, id, self.on_help)
148        self.menu_bar.Append(menu_help, "&Help")
149
150        self.SetMenuBar(self.menu_bar)
151        self.im_show(image)
152
153    def on_set_data(self, event):
154        """
155        Rescale the x y range, make 2D data and send it to data explore
156        """
157        title = self.file_name
158        self.panel = SetDialog(parent=self, title=title, image=self.data)
159        self.panel.ShowModal()
160
161    def on_help(self, event):
162        """
163        Bring up Image Viewer Documentation from the image viewer window
164        whenever the help menu item "how to" is clicked. Calls
165        DocumentationWindow with the path of the location within the
166        documentation tree (after /doc/ ....".
167
168        :param evt: Triggers on clicking "how to" in help menu
169        """
170
171        _TreeLocation = "user/sasgui/perspectives/calculator/"
172        _TreeLocation += "image_viewer_help.html"
173        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
174                                          "Image Viewer Help")
175
176
177class SetDialog(wx.Dialog):
178    """
179    Dialog for Data Set
180    """
181    def __init__(self, parent, id= -1, title="Convert to Data", image=None,
182                 size=(_DIALOG_WIDTH, 270)):
183        wx.Dialog.__init__(self, parent, id, title, size)
184        # parent
185        self.parent = parent
186        self.base = parent.parent
187        self.title = title
188        self.image = np.array(image)
189        self.z_ctrl = None
190        self.xy_ctrls = []
191        self.is_png = self._get_is_png()
192        self._build_layout()
193        my_title = "Convert Image to Data - %s -" % self.title
194        self.SetTitle(my_title)
195        self.SetSize(size)
196
197    def _get_is_png(self):
198        """
199        Get if the image file is png
200        """
201        _, extension = os.path.splitext(self.title)
202        return extension.lower() == '.png'
203
204    def _build_layout(self):
205        """
206        Layout
207        """
208        vbox = wx.BoxSizer(wx.VERTICAL)
209        zbox = wx.BoxSizer(wx.HORIZONTAL)
210        xbox = wx.BoxSizer(wx.HORIZONTAL)
211        ybox = wx.BoxSizer(wx.HORIZONTAL)
212        btnbox = wx.BoxSizer(wx.VERTICAL)
213
214        sb_title = wx.StaticBox(self, -1, 'Transform Axes')
215        boxsizer = wx.StaticBoxSizer(sb_title, wx.VERTICAL)
216        z_title = wx.StaticText(self, -1, 'z values (range: 0 - 255) to:')
217        ztime_title = wx.StaticText(self, -1, 'z *')
218        x_title = wx.StaticText(self, -1, 'x values from pixel # to:')
219        xmin_title = wx.StaticText(self, -1, 'xmin:')
220        xmax_title = wx.StaticText(self, -1, 'xmax:')
221        y_title = wx.StaticText(self, -1, 'y values from pixel # to:')
222        ymin_title = wx.StaticText(self, -1, 'ymin: ')
223        ymax_title = wx.StaticText(self, -1, 'ymax:')
224        z_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH , 20),
225                                style=wx.TE_PROCESS_ENTER)
226
227        xmin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
228                                style=wx.TE_PROCESS_ENTER)
229        xmax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
230                                style=wx.TE_PROCESS_ENTER)
231        ymin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
232                                style=wx.TE_PROCESS_ENTER)
233        ymax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
234                                style=wx.TE_PROCESS_ENTER)
235        z_ctl.SetValue('1.0')
236        xmin_ctl.SetValue('-0.3')
237        xmax_ctl.SetValue('0.3')
238        ymin_ctl.SetValue('-0.3')
239        ymax_ctl.SetValue('0.3')
240        z_ctl.Bind(wx.EVT_TEXT, self._on_z_enter)
241        xmin_ctl.Bind(wx.EVT_TEXT, self._onparam)
242        xmax_ctl.Bind(wx.EVT_TEXT, self._onparam)
243        ymin_ctl.Bind(wx.EVT_TEXT, self._onparam)
244        ymax_ctl.Bind(wx.EVT_TEXT, self._onparam)
245        xbox.AddMany([(x_title , 0, wx.LEFT, 0),
246                      (xmin_title , 0, wx.LEFT, 10),
247                      (xmin_ctl , 0, wx.LEFT, 10),
248                      (xmax_title , 0, wx.LEFT, 10),
249                      (xmax_ctl , 0, wx.LEFT, 10)])
250        ybox.AddMany([(y_title , 0, wx.LEFT, 0),
251                      (ymin_title , 0, wx.LEFT, 10),
252                      (ymin_ctl , 0, wx.LEFT, 10),
253                      (ymax_title , 0, wx.LEFT, 10),
254                      (ymax_ctl , 0, wx.LEFT, 10)])
255        zbox.AddMany([(z_title , 0, wx.LEFT, 0),
256                      (ztime_title, 0, wx.LEFT, 10),
257                      (z_ctl , 0, wx.LEFT, 7),
258                      ])
259        msg = "The data rescaled will show up in the Data Explorer. \n"
260        msg += "*Note: Recommend to use an image with 8 bit Grey \n"
261        msg += "  scale (and with No. of pixels < 300 x 300).\n"
262        msg += "  Otherwise, z = 0.299R + 0.587G + 0.114B."
263        note_txt = wx.StaticText(self, -1, msg)
264        note_txt.SetForegroundColour("black")
265        hbox = wx.BoxSizer(wx.HORIZONTAL)
266        okButton = wx.Button(self, -1, 'OK')
267        okButton.Bind(wx.EVT_BUTTON, self.on_set)
268        cancelButton = wx.Button(self, -1, 'Cancel')
269        cancelButton.Bind(wx.EVT_BUTTON, self.OnClose)
270        btnbox.Add(okButton, 0, wx.LEFT | wx.BOTTOM, 5)
271        btnbox.Add(cancelButton, 0, wx.LEFT | wx.TOP, 5)
272        hbox.Add(note_txt, 0, wx.LEFT, 5)
273        hbox.Add(btnbox, 0, wx.LEFT, 15)
274        vbox.Add((10, 15))
275        boxsizer.Add(xbox, 1, wx.LEFT | wx.BOTTOM, 5)
276        boxsizer.Add(ybox, 1, wx.LEFT | wx.BOTTOM, 5)
277        boxsizer.Add(zbox, 1, wx.LEFT | wx.BOTTOM, 5)
278        vbox.Add(boxsizer, 0, wx.LEFT, 20)
279        vbox.Add(hbox, 0, wx.LEFT | wx.TOP, 15)
280        okButton.SetFocus()
281        # set sizer
282        self.SetSizer(vbox)
283        #pos = self.parent.GetPosition()
284        #self.SetPosition(pos)
285        self.z_ctrl = z_ctl
286        self.xy_ctrls = [[xmin_ctl, xmax_ctl], [ymin_ctl, ymax_ctl]]
287
288    def _onparamEnter(self, event=None):
289        """
290        By pass original txtcrl binding
291        """
292        pass
293
294    def _onparam(self, event=None):
295        """
296        Set to default
297        """
298        item = event.GetEventObject()
299        self._check_ctrls(item)
300
301    def _check_ctrls(self, item, is_button=False):
302        """
303        """
304        flag = True
305        item.SetBackgroundColour("white")
306        try:
307            val = float(item.GetValue())
308            if val < -10.0 or val > 10.0:
309                item.SetBackgroundColour("pink")
310                item.Refresh()
311                flag = False
312        except:
313            item.SetBackgroundColour("pink")
314            item.Refresh()
315            flag = False
316        if not flag and is_button:
317            err_msg = "The allowed range of the min and max values are \n"
318            err_msg += "between -10 and 10."
319            if self.base is not None:
320                wx.PostEvent(self.base, StatusEvent(status=err_msg,
321                                                    info="error"))
322            else:
323                print(err_msg)
324        return flag
325
326    def _on_z_enter(self, event=None):
327        """
328        On z factor enter
329        """
330        item = event.GetEventObject()
331        self._check_z_ctrl(item)
332
333    def _check_z_ctrl(self, item, is_button=False):
334        """
335        """
336        flag = True
337        item.SetBackgroundColour("white")
338        try:
339            val = float(item.GetValue())
340            if val <= 0:
341                item.SetBackgroundColour("pink")
342                item.Refresh()
343                flag = False
344        except:
345            item.SetBackgroundColour("pink")
346            item.Refresh()
347            flag = False
348        if not flag and is_button:
349            err_msg = "The z scale value should be larger than 0."
350            if self.base is not None:
351                wx.PostEvent(self.base, StatusEvent(status=err_msg,
352                                                    info="error"))
353            else:
354                print(err_msg)
355        return flag
356
357    def on_set(self, event):
358        """
359        Set image as data
360        """
361        event.Skip()
362        # Check the textctrl values
363        for item_list in self.xy_ctrls:
364            for item in item_list:
365                 if not self._check_ctrls(item, True):
366                     return
367        if not self._check_z_ctrl(self.z_ctrl, True):
368            return
369        try:
370            image = self.image
371            xmin = float(self.xy_ctrls[0][0].GetValue())
372            xmax = float(self.xy_ctrls[0][1].GetValue())
373            ymin = float(self.xy_ctrls[1][0].GetValue())
374            ymax = float(self.xy_ctrls[1][1].GetValue())
375            zscale = float(self.z_ctrl.GetValue())
376            self.convert_image(image, xmin, xmax, ymin, ymax, zscale)
377        except:
378            err_msg = "Error occurred while converting Image to Data."
379            if self.base is not None:
380                wx.PostEvent(self.base, StatusEvent(status=err_msg,
381                                                    info="error"))
382            else:
383                print(err_msg)
384
385        self.EndModal(wx.ID_OK)
386
387    def convert_image(self, rgb, xmin, xmax, ymin, ymax, zscale):
388        """
389        Convert image to data2D
390        """
391        x_len = len(rgb[0])
392        y_len = len(rgb)
393        x_vals = np.linspace(xmin, xmax, num=x_len)
394        y_vals = np.linspace(ymin, ymax, num=y_len)
395        # Instantiate data object
396        output = Data2D()
397        output.filename = os.path.basename(self.title)
398        output.id = output.filename
399        detector = Detector()
400        detector.pixel_size.x = None
401        detector.pixel_size.y = None
402        # Store the sample to detector distance
403        detector.distance = None
404        output.detector.append(detector)
405        # Initiazed the output data object
406        output.data = zscale * self.rgb2gray(rgb)
407        output.err_data = np.zeros([x_len, y_len])
408        output.mask = np.ones([x_len, y_len], dtype=bool)
409        output.xbins = x_len
410        output.ybins = y_len
411        output.x_bins = x_vals
412        output.y_bins = y_vals
413        output.qx_data = np.array(x_vals)
414        output.qy_data = np.array(y_vals)
415        output.xmin = xmin
416        output.xmax = xmax
417        output.ymin = ymin
418        output.ymax = ymax
419        output.xaxis(r'\rm{Q_{x}}', r'\AA^{-1}')
420        output.yaxis(r'\rm{Q_{y}}', r'\AA^{-1}')
421        # Store loading process information
422        output.meta_data['loader'] = self.title.split('.')[-1] + "Reader"
423        output.is_data = True
424        output = reader2D_converter(output)
425        if self.base is not None:
426            data = self.base.create_gui_data(output, self.title)
427            self.base.add_data({data.id:data})
428
429    def rgb2gray(self, rgb):
430        """
431        RGB to Grey
432        """
433        if self.is_png:
434            # png image limits: 0 to 1, others 0 to 255
435            #factor = 255.0
436            rgb = rgb[::-1]
437        if rgb.ndim == 2:
438            grey = np.rollaxis(rgb, axis=0)
439        else:
440            red, green, blue = np.rollaxis(rgb[..., :3], axis= -1)
441            grey = 0.299 * red + 0.587 * green + 0.114 * blue
442        max_i = rgb.max()
443        factor = 255.0 / max_i
444        grey *= factor
445        return np.array(grey)
446
447    def OnClose(self, event):
448        """
449        Close event
450        """
451        # clear event
452        event.Skip()
453        self.EndModal(wx.ID_CANCEL)
454        #self.Destroy()
455
456if __name__ == "__main__":
457    app = wx.App()
458    ImageView(None).load()
459    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.