source: sasview/src/sas/qtgui/Utilities/ImageViewer.py @ 82efbe9

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 82efbe9 was 5584dee, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Minor stylistical changes to ImageViewer?.py after pylint

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