source: sasview/src/sas/qtgui/Utilities/ImageViewer.py @ 1942f63

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 1942f63 was 1942f63, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Merged ESS_GUI_image_viewer

  • Property mode set to 100644
File size: 10.8 KB
Line 
1"""
2Image viewer widget.
3"""
4from PyQt5 import QtCore
5from PyQt5 import QtGui
6from PyQt5 import QtWidgets
7
8import os
9import logging
10import numpy as np
11import matplotlib
12matplotlib.interactive(False)
13import matplotlib.image as mpimg
14
15from sas.sascalc.dataloader.manipulations import reader2D_converter
16
17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18from sas.qtgui.Plotting.Plotter2D import Plotter2D
19from sas.qtgui.Plotting.PlotterData import Data2D
20from sas.sascalc.dataloader.data_info import Detector
21
22# Local UI
23from sas.qtgui.Utilities.UI.ImageViewerUI import Ui_ImageViewerUI
24from sas.qtgui.Utilities.UI.ImageViewerOptionsUI import Ui_ImageViewerOptionsUI
25
26class ImageViewer(QtWidgets.QMainWindow, Ui_ImageViewerUI):
27    """
28    Implemented as QMainWindow to enable easy menus
29    """
30    def __init__(self, parent=None):
31        super(ImageViewer, self).__init__(parent._parent)
32
33        self.parent = parent
34        self.setupUi(self)
35
36        # add plotter to the frame
37        self.plotter = None
38        self.hbox = None
39
40        # disable menu items on empty canvas
41        self.disableMenus()
42
43        # set up signal callbacks
44        self.addCallbacks()
45
46        # set up menu item triggers
47        self.addTriggers()
48
49    def addCallbacks(self):
50        pass
51
52    def disableMenus(self):
53        """
54        All menu items but "Load File" and "Help" should be disabled
55        when no data is present
56        """
57        self.actionSave_Image.setEnabled(False)
58        self.actionPrint_Image.setEnabled(False)
59        self.actionCopy_Image.setEnabled(False)
60        self.actionConvert_to_Data.setEnabled(False)
61
62    def enableMenus(self):
63        """
64        Enable all menu items when data is present
65        """
66        self.actionSave_Image.setEnabled(True)
67        self.actionPrint_Image.setEnabled(True)
68        self.actionCopy_Image.setEnabled(True)
69        self.actionConvert_to_Data.setEnabled(True)
70
71    def addTriggers(self):
72        """
73        Trigger definitions for all menu/toolbar actions.
74        """
75        # File
76        self.actionLoad_Image.triggered.connect(self.actionLoadImage)
77        self.actionSave_Image.triggered.connect(self.actionSaveImage)
78        self.actionPrint_Image.triggered.connect(self.actionPrintImage)
79        # Edit
80        self.actionCopy_Image.triggered.connect(self.actionCopyImage)
81        # Image
82        self.actionConvert_to_Data.triggered.connect(self.actionConvertToData)
83        # Help
84        self.actionHow_To.triggered.connect(self.actionHowTo)
85
86    def actionLoadImage(self):
87        """
88        Image loader given files extensions
89        """
90        wildcards = "Images (*.bmp *.gif *jpeg *jpg *.png *tif *.tiff) ;;"\
91                    "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        filepath = QtWidgets.QFileDialog.getOpenFileName(
99            self, "Choose a file", "", wildcards)[0]
100
101        if filepath:
102            self.showImage(filepath)
103
104    def actionSaveImage(self):
105        """
106        Use the internal MPL method for saving to file
107        """
108        if self.plotter is not None:
109            self.plotter.onImageSave()
110
111    def actionPrintImage(self):
112        """
113        Display printer dialog and print the MPL widget area
114        """
115        if self.plotter is not None:
116            self.plotter.onImagePrint()
117
118    def actionCopyImage(self):
119        """
120        Copy MPL widget area to buffer
121        """
122        if self.plotter is not None:
123           self.plotter.onClipboardCopy()
124
125    def actionConvertToData(self):
126        """
127        Show the options dialog and if accepted, send data to conversion
128        """
129        options = ImageViewerOptions(self)
130        if options.exec_() != QtWidgets.QDialog.Accepted:
131            return
132
133        (xmin, xmax, ymin, ymax, zscale) = options.getState()
134        image = self.image
135        try:
136            self.convertImage(image, xmin, xmax, ymin, ymax, zscale)
137        except:
138            err_msg = "Error occurred while converting Image to Data."
139            logging.error(err_msg)
140
141        pass
142
143    def actionHowTo(self):
144        ''' Send the image viewer help URL to the help viewer '''
145        location = "/user/qtgui/Calculators/image_viewer_help.html"
146        self.parent.showHelp(location)
147
148    def addPlotter(self):
149        """
150        Add a new plotter to the frame
151        """
152        self.plotter = Plotter2D(self, quickplot=True)
153
154        # remove existing layout
155        if self.hbox is not None:
156            for i in range(self.hbox.count()):
157                layout_item = self.hbox.itemAt(i)
158                self.hbox.removeItem(layout_item)
159            self.hbox.addWidget(self.plotter)
160        else:
161            # add the plotter to the QLayout
162            self.hbox = QtWidgets.QHBoxLayout()
163            self.hbox.addWidget(self.plotter)
164            self.imgFrame.setLayout(self.hbox)
165
166    def showImage(self, filename):
167        """
168        Show the requested image in the main frame
169        """
170        self.filename = os.path.basename(filename)
171        _, extension = os.path.splitext(self.filename)
172        try:
173            # Note that matplotlib only reads png natively.
174            # Any other formats (tiff, jpeg, etc) are passed
175            # to PIL which seems to have a problem in version
176            # 1.1.7 that causes a close error which shows up in
177            # the log file.  This does not seem to have any adverse
178            # effects.  PDB   --- September 17, 2017.
179            self.image = mpimg.imread(filename)
180            self.is_png = extension.lower() == '.png'
181            self.addPlotter()
182            ax = self.plotter.ax
183            flipped_image = np.flipud(self.image)
184            origin = None
185            if self.is_png:
186                origin='lower'
187            self.plotter.imageShow(flipped_image, origin=origin)
188            if not self.is_png:
189                ax.set_ylim(ax.get_ylim()[::-1])
190            ax.set_xlabel('x [pixel]')
191            ax.set_ylabel('y [pixel]')
192            self.plotter.figure.subplots_adjust(left=0.15, bottom=0.1,
193                                        right=0.95, top=0.95)
194            title = 'Picture: ' + self.filename
195            self.setWindowTitle(title)
196            self.plotter.draw()
197        except IOError as ex:
198            err_msg = "Failed to load '%s'.\n" % self.filename
199            logging.error(err_msg)
200            return
201        except Exception as ex:
202            err_msg = "Failed to show '%s'.\n" % self.filename
203            logging.error(err_msg)
204            return
205
206        # Loading successful - enable menu items
207        self.enableMenus()
208
209    def convertImage(self, rgb, xmin, xmax, ymin, ymax, zscale):
210        """
211        Convert image to data2D
212        """
213        x_len = len(rgb[0])
214        y_len = len(rgb)
215        x_vals = np.linspace(xmin, xmax, num=x_len)
216        y_vals = np.linspace(ymin, ymax, num=y_len)
217        # Instantiate data object
218        output = Data2D()
219        output.filename = self.filename
220        output.id = output.filename
221        detector = Detector()
222        detector.pixel_size.x = None
223        detector.pixel_size.y = None
224        # Store the sample to detector distance
225        detector.distance = None
226        output.detector.append(detector)
227        # Initiazed the output data object
228        output.data = zscale * self.rgb2gray(rgb)
229        output.err_data = np.zeros([x_len, y_len])
230        output.mask = np.ones([x_len, y_len], dtype=bool)
231        output.xbins = x_len
232        output.ybins = y_len
233        output.x_bins = x_vals
234        output.y_bins = y_vals
235        output.qx_data = np.array(x_vals)
236        output.qy_data = np.array(y_vals)
237        output.xmin = xmin
238        output.xmax = xmax
239        output.ymin = ymin
240        output.ymax = ymax
241        output.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
242        output.yaxis('\\rm{Q_{y}}', '\AA^{-1}')
243        # Store loading process information
244        output.meta_data['loader'] = self.filename.split('.')[-1] + "Reader"
245        output.is_data = True
246        try:
247            output = reader2D_converter(output)
248        except Exception as ex:
249            err_msg = "Image conversion failed: '%s'.\n" % str(ex)
250            logging.error(err_msg)
251
252        # Create item and add to the data explorer
253        try:
254            item = GuiUtils.createModelItemWithPlot(output, output.filename)
255            self.parent.communicate.updateModelFromPerspectiveSignal.emit(item)
256        except Exception as ex:
257            err_msg = "Failed to create new index '%s'.\n" % str(ex)
258            logging.error(err_msg)
259
260    def rgb2gray(self, rgb):
261        """
262        RGB to Grey
263        """
264        if self.is_png:
265            # png image limits: 0 to 1, others 0 to 255
266            #factor = 255.0
267            rgb = rgb[::-1]
268        if rgb.ndim == 2:
269            grey = np.rollaxis(rgb, axis=0)
270        else:
271            red, green, blue = np.rollaxis(rgb[..., :3], axis= -1)
272            grey = 0.299 * red + 0.587 * green + 0.114 * blue
273        max_i = rgb.max()
274        factor = 255.0 / max_i
275        grey *= factor
276        return np.array(grey)
277
278class ImageViewerOptions(QtWidgets.QDialog, Ui_ImageViewerOptionsUI):
279    """
280    Logics for the image viewer options UI
281    """
282    def __init__(self, parent=None):
283        super(ImageViewerOptions, self).__init__(parent)
284
285        self.parent = parent
286        self.setupUi(self)
287
288        # fill in defaults
289        self.addDefaults()
290
291        # add validators
292        self.addValidators()
293
294    def addDefaults(self):
295        """
296        Fill out textedits with default values
297        """
298        zscale_default = 1.0
299        xmin_default = -0.3
300        xmax_default = 0.3
301        ymin_default = -0.3
302        ymax_default = 0.3
303
304        self.txtZmax.setText(str(zscale_default))
305        self.txtXmin.setText(str(xmin_default))
306        self.txtXmax.setText(str(xmax_default))
307        self.txtYmin.setText(str(ymin_default))
308        self.txtYmax.setText(str(ymax_default))
309
310    def addValidators(self):
311        """
312        Define simple validators on line edits
313        """
314        self.txtXmin.setValidator(GuiUtils.DoubleValidator())
315        self.txtXmax.setValidator(GuiUtils.DoubleValidator())
316        self.txtYmin.setValidator(GuiUtils.DoubleValidator())
317        self.txtYmax.setValidator(GuiUtils.DoubleValidator())
318        zvalidator = GuiUtils.DoubleValidator()
319        zvalidator.setBottom(0.0)
320        zvalidator.setTop(255.0)
321        self.txtZmax.setValidator(zvalidator)
322
323    def getState(self):
324        """
325        return current state of the widget
326        """
327        zscale = float(self.txtZmax.text())
328        xmin = float(self.txtXmin.text())
329        xmax = float(self.txtXmax.text())
330        ymin = float(self.txtYmin.text())
331        ymax = float(self.txtYmax.text())
332
333        return (xmin, xmax, ymin, ymax, zscale)
334
Note: See TracBrowser for help on using the repository browser.