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

Last change on this file since d07f863 was a1b8fee, checked in by andyfaff, 8 years ago

MAINT: from future import print_function

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