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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since eec63a8 was eec63a8, checked in by Mathieu Doucet <doucetm@…>, 9 years ago

Make sure OSX build can load images.

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