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

Last change on this file since a9279cc was 25b9707a, checked in by lewis, 8 years ago

Remove menu icons from Image Viewer tool (references #368)

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