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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 6479f3a was 6479f3a, checked in by butler, 7 years ago

Add file choice

Add file choice of all allowed image files (as opposed to all files) and
make the default choice.
Need to update images in rst help. Also seems to be a bug in
matplotlib.image being used (not properly closing the file after a
read?" the first should be fixed before merging. Second may leave for
another time.

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