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

Last change on this file since 1b061a31 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
RevLine 
[3e001f9]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')
[d85c194]9from sas.sasgui.guiframe.local_perspectives.plotting.SimplePlot import PlotFrame
[3e001f9]10#import matplotlib.pyplot as plt
11import matplotlib.image as mpimg
12import matplotlib.colors as colors
[d85c194]13from sas.sasgui.guiframe.events import StatusEvent
14from sas.sasgui.perspectives.calculator.calculator_widgets import InputTextCtrl
[b699768]15from sas.sascalc.dataloader.data_info import Data2D
16from sas.sascalc.dataloader.data_info import Detector
17from sas.sascalc.dataloader.manipulations import reader2D_converter
[d85c194]18from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[d90f91b]19
[3e001f9]20_BOX_WIDTH = 60
[5cfa884]21IS_WIN = True
[3e001f9]22if sys.platform.count("win32") > 0:
23    _DIALOG_WIDTH = 400
24else:
25    _DIALOG_WIDTH = 480
[5cfa884]26    IS_WIN = False
[3e001f9]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
[49ab5d7]38
[3e001f9]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]')
[49ab5d7]68                ax.figure.subplots_adjust(left=0.15, bottom=0.1,
[3e001f9]69                                          right=0.95, top=0.95)
[49ab5d7]70                plot_frame.SetTitle('Picture -- %s --' % basename)
[3e001f9]71                plot_frame.Show(True)
72                if parent != None:
73                    parent.put_icon(plot_frame)
74            except:
[49ab5d7]75                err_msg += "Failed to load '%s'.\n" % basename
[3e001f9]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
[49ab5d7]81
[3e001f9]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()
[49ab5d7]89        dlg = wx.FileDialog(self.parent, "Image Viewer: Choose a image file",
90                            location, "", "", style=wx.FD_OPEN | wx.FD_MULTIPLE)
[3e001f9]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    """
[49ab5d7]102    def __init__(self, parent, id, title, image=None, scale='log_{10}',
103                 size=wx.Size(550, 470)):
[3e001f9]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
[25b9707a]110        PlotFrame.__init__(self, parent, id, title, scale, size,
111            show_menu_icons=False)
[3e001f9]112        self.parent = parent
[49ab5d7]113        self.data = image
[3e001f9]114        self.file_name = title
[49ab5d7]115
[3e001f9]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")
[49ab5d7]129
[3e001f9]130        self.SetMenuBar(self.menu_bar)
131        self.im_show(image)
[49ab5d7]132
[3e001f9]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
[49ab5d7]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
[d90f91b]145        DocumentationWindow with the path of the location within the
[49ab5d7]146        documentation tree (after /doc/ ....".
147
[d90f91b]148        :param evt: Triggers on clicking "how to" in help menu
[3e001f9]149        """
[49ab5d7]150
[d0248bd]151        _TreeLocation = "user/sasgui/perspectives/calculator/"
152        _TreeLocation += "image_viewer_help.html"
[3db44fb]153        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
154                                          "Image Viewer Help")
[49ab5d7]155
156
[3e001f9]157class SetDialog(wx.Dialog):
158    """
159    Dialog for Data Set
160    """
[49ab5d7]161    def __init__(self, parent, id= -1, title="Convert to Data", image=None,
[3e001f9]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()
[49ab5d7]173        my_title = "Convert Image to Data - %s -" % self.title
[3e001f9]174        self.SetTitle(my_title)
175        self.SetSize(size)
[49ab5d7]176
[3e001f9]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'
[49ab5d7]183
[3e001f9]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)
[49ab5d7]193
[3e001f9]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)
[49ab5d7]206
[3e001f9]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)
[49ab5d7]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),
[3e001f9]229                      (xmax_ctl , 0, wx.LEFT, 10)])
[49ab5d7]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),
[3e001f9]236                      (ztime_title, 0, wx.LEFT, 10),
[49ab5d7]237                      (z_ctl , 0, wx.LEFT, 7),
238                      ])
[3e001f9]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        """
[49ab5d7]272        pass
273
[3e001f9]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:
[49ab5d7]287            val = float(item.GetValue())
[3e001f9]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:
[49ab5d7]300                wx.PostEvent(self.base, StatusEvent(status=err_msg,
[3e001f9]301                                                    info="error"))
302            else:
303                print err_msg
304        return flag
305
[49ab5d7]306    def _on_z_enter(self, event=None):
[3e001f9]307        """
308        On z factor enter
309        """
310        item = event.GetEventObject()
311        self._check_z_ctrl(item)
[49ab5d7]312
[3e001f9]313    def _check_z_ctrl(self, item, is_button=False):
314        """
315        """
316        flag = True
317        item.SetBackgroundColour("white")
318        try:
[49ab5d7]319            val = float(item.GetValue())
[3e001f9]320            if val <= 0:
321                item.SetBackgroundColour("pink")
322                item.Refresh()
323                flag = False
324        except:
325            item.SetBackgroundColour("pink")
[49ab5d7]326            item.Refresh()
[3e001f9]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:
[49ab5d7]331                wx.PostEvent(self.base, StatusEvent(status=err_msg,
[3e001f9]332                                                    info="error"))
333            else:
334                print err_msg
[49ab5d7]335        return flag
336
[3e001f9]337    def on_set(self, event):
338        """
[49ab5d7]339        Set image as data
[3e001f9]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:
[49ab5d7]360                wx.PostEvent(self.base, StatusEvent(status=err_msg,
[3e001f9]361                                                    info="error"))
362            else:
363                print err_msg
[49ab5d7]364
[3e001f9]365        self.OnClose(event)
[49ab5d7]366
[3e001f9]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})
[49ab5d7]408
[3e001f9]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:
[49ab5d7]418            grey = np.rollaxis(rgb, axis=0)
[3e001f9]419        else:
[49ab5d7]420            red, green, blue = np.rollaxis(rgb[..., :3], axis= -1)
[3e001f9]421            grey = 0.299 * red + 0.587 * green + 0.114 * blue
422        max_i = rgb.max()
423        factor = 255.0 / max_i
[49ab5d7]424        grey *= factor
[3e001f9]425        return np.array(grey)
[49ab5d7]426
[3e001f9]427    def OnClose(self, event):
428        """
429        Close event
430        """
431        # clear event
432        event.Skip()
433        self.Destroy()
[49ab5d7]434
[3e001f9]435if __name__ == "__main__":
[49ab5d7]436    app = wx.App()
[3e001f9]437    ImageView(None).load()
[49ab5d7]438    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.