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

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 c245ca4 was c245ca4, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Added image file selector for the Image Viewer Tool. Fixes #898

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