source: sasview/src/sas/qtgui/Calculators/ResolutionCalculatorPanel.py @ 7fb471d

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 7fb471d was 7fb471d, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Update for unit tests and minor functionality quirks

  • Property mode set to 100644
File size: 31.0 KB
Line 
1"""
2This object is a small tool to allow user to quickly
3determine the variance in q  from the
4instrumental parameters.
5"""
6from PyQt4 import QtGui
7from PyQt4 import QtCore
8
9from twisted.internet import threads
10import sas.qtgui.Utilities.GuiUtils as GuiUtils
11from sas.qtgui.Plotting.PlotterData import Data2D
12from sas.qtgui.Plotting.Plotter2D import Plotter2DWidget
13from sas.sascalc.calculator.resolution_calculator import ResolutionCalculator
14import matplotlib.patches as patches
15
16import numpy
17import sys
18import logging
19import os
20import re
21
22from .UI.ResolutionCalculatorPanelUI import Ui_ResolutionCalculatorPanel
23
24_SOURCE_MASS = {'Alpha': 6.64465620E-24,
25                'Deuteron': 3.34358320E-24,
26                'Neutron': 1.67492729E-24,
27                'Photon': 0.0,
28                'Proton': 1.67262137E-24,
29                'Triton': 5.00826667E-24}
30
31BG_WHITE = "background-color: rgb(255, 255, 255);"
32BG_RED = "background-color: rgb(244, 170, 164);"
33
34
35class ResolutionCalculatorPanel(QtGui.QDialog, Ui_ResolutionCalculatorPanel):
36    """
37    compute resolution in 2D
38    """
39    def __init__(self, parent=None):
40        super(ResolutionCalculatorPanel, self).__init__()
41        self.setupUi(self)
42        self.manager = parent
43
44        # New font to display angstrom symbol
45        new_font = 'font-family: -apple-system, "Helvetica Neue", "Ubuntu";'
46        self.lblUnitWavelength.setStyleSheet(new_font)
47        self.lblUnitQx.setStyleSheet(new_font)
48        self.lblUnitQy.setStyleSheet(new_font)
49        self.lblUnitSigmax.setStyleSheet(new_font)
50        self.lblUnitSigmay.setStyleSheet(new_font)
51        self.lblUnitSigmalamd.setStyleSheet(new_font)
52        self.lblUnit1DSigma.setStyleSheet(new_font)
53
54        # by default Spectrum label and cbCustomSpectrum are not visible
55        self.cbCustomSpectrum.setVisible(False)
56        self.lblSpectrum.setVisible(False)
57        # self.onReset()
58
59        # change index of comboboxes
60        self.cbWaveColor.currentIndexChanged.connect(self.onSelectWaveColor)
61        self.cbCustomSpectrum.currentIndexChanged.connect(self.onSelectCustomSpectrum)
62
63        # push buttons
64        self.cmdClose.clicked.connect(self.accept)
65        self.cmdHelp.clicked.connect(self.onHelp)
66        self.cmdCompute.clicked.connect(self.onCompute)
67        self.cmdReset.clicked.connect(self.onReset)
68
69        # input defaults
70        self.qx = []
71        self.qy = []
72        # dQ defaults
73        self.sigma_r = None
74        self.sigma_phi = None
75        self.sigma_1d = None
76
77        # number of bins for wavelength and wavelength spread
78        self.num_wave = 10
79        self.spectrum_dic = {}
80
81        # dQ 2d image
82        self.image = None
83        # Source selection dic
84        self.source_mass = _SOURCE_MASS
85        # detector coordinate of estimation of sigmas
86        self.det_coordinate = 'cartesian'
87
88        self.resolution = ResolutionCalculator()
89        self.spectrum_dic['Add new'] = ''
90        self.spectrum_dic['Flat'] = self.resolution.get_default_spectrum()
91        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
92
93        # validators
94        self.txtWavelength.editingFinished.connect(self.checkWavelength)
95        self.txtWavelengthSpread.editingFinished.connect(self.checkWavelengthSpread)
96
97        self.txtDetectorPixSize.editingFinished.connect(self.checkPixels)
98        self.txtDetectorSize.editingFinished.connect(self.checkPixels)
99
100        self.txtSourceApertureSize.editingFinished.connect(self.checkAperture)
101        self.txtSampleApertureSize.editingFinished.connect(self.checkAperture)
102
103        self.txtQx.editingFinished.connect(self.checkQx_y)
104        self.txtQy.editingFinished.connect(self.checkQx_y)
105
106        # double validator
107        self.txtSource2SampleDistance.setValidator(QtGui.QDoubleValidator())
108        self.txtSample2DetectorDistance.setValidator(QtGui.QDoubleValidator())
109        self.txtSampleOffset.setValidator(QtGui.QDoubleValidator())
110
111        # call compute to calculate with default values
112        self.createTemplate2DPlot()
113        #self.onCompute()
114
115    # #################################
116    # Validators: red background in line edits when wrong input
117    # and display of info logging message
118    # #################################
119
120    def checkWavelength(self):
121        """ Validator for Wavelength
122         if TOF, wavelength = min - max else only one number """
123        text_edit = self.txtWavelength  # self.sender()
124        if text_edit.isModified():
125            text_edit.setStyleSheet(BG_WHITE)
126            input_string = str(text_edit.text())
127            if self.cbWaveColor.currentText() != 'TOF':
128                input_wavelength = re.match('\d+\.?\d*', input_string)
129                if input_wavelength is None:
130                    text_edit.setStyleSheet(BG_RED)
131                    self.cmdCompute.setEnabled(False)
132                    logging.info('Wavelength has to be a number.')
133                else:
134                    text_edit.setStyleSheet(BG_WHITE)
135                    self.cmdCompute.setEnabled(True)
136            else:
137                interval_wavelength = re.match('^\d+\.?\d*\s*-\s*\d+\.?\d*$',
138                                               input_string)
139
140                if interval_wavelength is None:
141                    text_edit.setStyleSheet(BG_RED)
142                    self.cmdCompute.setEnabled(False)
143                    logging.info("Wavelength's input has to be an interval: "
144                                 "min - max.")
145                else:
146                    # check on min < max
147                    [wavelength_min, wavelength_max] = \
148                        re.findall('\d+\.?\d*', interval_wavelength.group())
149
150                    if float(wavelength_min) >= float(wavelength_max):
151                        text_edit.setStyleSheet(BG_RED)
152                        self.cmdCompute.setEnabled(False)
153                        logging.info("Wavelength: min must be smaller than max.")
154
155                    else:
156                        text_edit.setStyleSheet(BG_WHITE)
157                        self.cmdCompute.setEnabled(True)
158
159    def checkWavelengthSpread(self):
160        """ Validator for WavelengthSpread
161         Input can be a 'number or min - max (; Number of bins)' """
162        text_edit = self.sender()
163
164        if text_edit.isModified():
165            text_edit.setStyleSheet(BG_WHITE)
166            if self.cbWaveColor.currentText() != 'TOF':
167                pattern = '^\d+\.?\d*(|;\s*\d+)$'
168                input_string = str(text_edit.text())
169                wavelength_spread_input = re.match(pattern, input_string)
170
171                if wavelength_spread_input is None:
172                    text_edit.setStyleSheet(BG_RED)
173                    self.cmdCompute.setEnabled(False)
174                    logging.info('Wavelength spread has to be specified: '
175                                 'single value or value; integer number of bins.')
176
177                else:
178                    split_input = wavelength_spread_input.group().split(';')
179                    self.num_wave = split_input[1] if len(split_input) > 1 else 10
180                    text_edit.setStyleSheet(BG_WHITE)
181                    self.cmdCompute.setEnabled(True)
182            else:
183                pattern = '^\d+\.?\d*\s*-\s*\d+\.?\d*(|;\s*\d+)$'
184                input_string = str(text_edit.text())
185                wavelength_spread_input = re.match(pattern, input_string)
186
187                if wavelength_spread_input is None:
188                    text_edit.setStyleSheet(BG_RED)
189                    self.cmdCompute.setEnabled(False)
190                    logging.info("Wavelength spread has to be specified: "
191                                 "doublet separated by '-' with optional "
192                                 "number of bins (given after ';'). "
193                                 "For example, 0.1 - 0.1 (; 20).")
194
195                else:
196                    split_input = wavelength_spread_input.group().split(';')
197                    self.num_wave = split_input[1] if len(
198                        split_input) > 1 else 10
199                    text_edit.setStyleSheet(BG_WHITE)
200                    self.cmdCompute.setEnabled(True)
201
202    def checkPixels(self):
203        """ Validator for detector pixel size and number """
204        text_edit = self.sender()
205
206        if text_edit.isModified():
207            text_edit.setStyleSheet(BG_WHITE)
208            pattern = '^\d+\.?\d*,\s*\d+\.?\d*$'
209            input_string = str(text_edit.text())
210            pixels_input = re.match(pattern, input_string)
211
212            if pixels_input is None:
213                text_edit.setStyleSheet(BG_RED)
214                self.cmdCompute.setEnabled(False)
215                logging.info('The input for the detector should contain 2 '
216                             'values separated by a comma.')
217
218            else:
219                text_edit.setStyleSheet(BG_WHITE)
220                self.cmdCompute.setEnabled(True)
221
222    def checkQx_y(self):
223        """ Validator for qx and qy inputs """
224        Q_modified = [self.txtQx.isModified(), self.txtQy.isModified()]
225        if any(Q_modified):
226            pattern = '^-?\d+\.?\d*(,\s*-?\d+\.?\d*)*$'
227            text_edit = self.txtQx if Q_modified[0] else self.txtQy
228            input_string = str(text_edit.text())
229            q_input = re.match(pattern, input_string)
230            if q_input is None:
231                text_edit.setStyleSheet(BG_RED)
232                self.cmdCompute.setEnabled(False)
233                logging.info('Qx and Qy should contain one or more comma-separated numbers.')
234            else:
235                text_edit.setStyleSheet(BG_WHITE)
236                self.cmdCompute.setEnabled(True)
237                qx = str(self.txtQx.text()).split(',')
238                qy = str(self.txtQy.text()).split(',')
239
240                if len(qx) == 1 and len(qy) > 1:
241                    fill_qx = ', '.join([qx[0]] * len(qy))
242                    self.txtQx.setText(fill_qx)
243
244                elif len(qy) == 1 and len(qx) > 1:
245                    fill_qy = ', '.join([qy[0]] * len(qx))
246                    self.txtQy.setText(fill_qy)
247
248                elif len(qx) != len(qy):
249                    text_edit.setStyleSheet(BG_RED)
250                    self.cmdCompute.setEnabled(False)
251                    logging.info(
252                        'Qx and Qy should have the same number of elements.')
253
254                else:
255                    text_edit.setStyleSheet(BG_WHITE)
256                    self.cmdCompute.setEnabled(True)
257
258    def checkAperture(self):
259        """ Validator for Sample and Source apertures"""
260        text_edit = self.sender()
261
262        if text_edit.isModified():
263            text_edit.setStyleSheet(BG_WHITE)
264            input_string = str(text_edit.text())
265            pattern = '^\d+\.?\d*(|,\s*\d+)$'
266            aperture_input = re.match(pattern, input_string)
267
268            if aperture_input is None:
269                text_edit.setStyleSheet(BG_RED)
270                self.cmdCompute.setEnabled(False)
271                logging.info('A circular aperture is defined by a single '
272                             'value (diameter). A rectangular aperture is '
273                             'defined by 2 values separated by a comma.')
274
275            else:
276                text_edit.setStyleSheet(BG_WHITE)
277                self.cmdCompute.setEnabled(True)
278
279    # #################################
280    # Slots associated with signals from comboboxes
281    # #################################
282
283    def onSelectWaveColor(self):
284        """ Modify layout of GUI when TOF selected: add elements
285        and modify default entry of Wavelength """
286        list_wdata = self.resolution.get_wave_list()
287        min_lambda = min(list_wdata[0])
288
289        min_wspread = min(list_wdata[1])
290        max_wspread = max(list_wdata[1])
291
292        if self.cbWaveColor.currentText() == 'TOF':
293            self.cbCustomSpectrum.setVisible(True)
294            self.lblSpectrum.setVisible(True)
295            # Get information about wavelength and spread
296
297            if len(list_wdata[0]) < 2:
298                max_lambda = 2 * min_lambda
299            else:
300                max_lambda = max(list_wdata[0])
301            self.txtWavelength.setText('{} - {}'.format(min_lambda, max_lambda))
302            self.txtWavelengthSpread.setText('{} - {}'.format(min_wspread,
303                                                    max_wspread))
304
305        else:
306            self.cbCustomSpectrum.setVisible(False)
307            self.lblSpectrum.setVisible(False)
308            # modify Wavelength line edit only if set for TOF (2 elements)
309
310            if len(self.txtWavelength.text().split('-')) >= 2:
311                self.txtWavelength.setText(str(min_lambda))
312                self.txtWavelengthSpread.setText(str(min_wspread))
313
314    def onSelectCustomSpectrum(self):
315        """ On Spectrum Combobox event"""
316        if self.cbCustomSpectrum.currentText() == 'Add New':
317            datafile = QtGui.QFileDialog.getOpenFileName(
318                self, "Choose a spectral distribution file", "",
319                "All files (*.*)",
320                QtGui.QFileDialog.DontUseNativeDialog)
321
322            if datafile is None or str(datafile) == '':
323                logging.info("No spectral distribution data chosen.")
324                self.cbCustomSpectrum.setCurrentIndex(0)
325                self.resolution.set_spectrum(self.spectrum_dic['Flat'])
326                return
327
328            try:
329                basename = os.path.basename(datafile)
330                if basename not in list(self.spectrum_dic.keys()):
331                    self.cbCustomSpectrum.addItem(basename)
332
333                input_f = open(datafile, 'r')
334                buff = input_f.read()
335                lines = buff.split('\n')
336
337                wavelength = []
338                intensity = []
339
340                for line in lines:
341                    toks = line.split()
342                    try:
343                        wave = float(toks[0])
344                        intens = float(toks[1])
345                        wavelength.append(wave)
346                        intensity.append(intens)
347                    except:
348                        logging.info('Could not extract values from file')
349            except:
350                raise
351
352            self.spectrum_dic[basename] = [wavelength, intensity]
353            self.resolution.set_spectrum(self.spectrum_dic[basename])
354        return
355
356    # #################################
357    # Slots associated with signals from push buttons
358    # #################################
359
360    def onHelp(self):
361        """
362        Bring up the Resolution Calculator Documentation whenever
363        the HELP button is clicked.
364        Calls Documentation Window with the path of the location within the
365        documentation tree (after /doc/ ....".
366        """
367        try:
368            location = GuiUtils.HELP_DIRECTORY_LOCATION + \
369                       "/user/sasgui/perspectives/calculator/resolution_calculator_help.html"
370            self.manager._helpView.load(QtCore.QUrl(location))
371            self.manager._helpView.show()
372
373        except AttributeError:
374            # No manager defined - testing and standalone runs
375            pass
376
377    def onReset(self):
378        # by default Spectrum label and cbCustomSpectrum are not visible
379        self.cbCustomSpectrum.setVisible(False)
380        self.lblSpectrum.setVisible(False)
381        # Comboboxes
382        self.cbCustomSpectrum.setCurrentIndex([self.cbCustomSpectrum.itemText(i)
383                                               for i in range(self.cbCustomSpectrum.count())].index('Flat'))
384        self.cbSource.setCurrentIndex([self.cbSource.itemText(i) for i in
385                                       range(self.cbSource.count())].index('Neutron'))
386        self.cbWaveColor.setCurrentIndex([self.cbWaveColor.itemText(i) for i
387                                          in range(self.cbWaveColor.count())].index('Monochromatic'))
388        # LineEdits
389        self.txtDetectorPixSize.setText('0.5, 0.5')
390        self.txtDetectorSize.setText('128, 128')
391        self.txtSample2DetectorDistance.setText('1000')
392        self.txtSampleApertureSize.setText('1.27')
393        self.txtSampleOffset.setText('0')
394        self.txtSource2SampleDistance.setText('1627')
395        self.txtSourceApertureSize.setText('3.81')
396        self.txtWavelength.setText('6.0')
397        self.txtWavelengthSpread.setText('0.125')
398        self.txtQx.setText('0.0')
399        self.txtQy.setText('0.0')
400        self.txt1DSigma.setText('0.0008289')
401        self.txtSigma_x.setText('0.0008288')
402        self.txtSigma_y.setText('0.0008288')
403        self.txtSigma_lamd.setText('3.168e-05')
404
405        self.image = None
406        self.source_mass = _SOURCE_MASS
407        self.det_coordinate = 'cartesian'
408        self.num_wave = 10
409        self.spectrum_dic = {}
410        self.spectrum_dic['Add new'] = ''
411        self.spectrum_dic['Flat'] = self.resolution.get_default_spectrum()
412        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
413        # Reset plot
414        self.onCompute()
415
416    # TODO Keep legacy validators??
417    def onCompute(self):
418        """
419        Execute the computation of resolution
420        """
421        # Q min max list default
422        qx_min = []
423        qx_max = []
424        qy_min = []
425        qy_max = []
426        # possible max qrange
427        self.resolution.qxmin_limit = 0
428        self.resolution.qxmax_limit = 0
429        self.resolution.qymin_limit = 0
430        self.resolution.qymax_limit = 0
431
432        try:
433            # Get all the values to compute
434            wavelength = self._str2longlist(self.txtWavelength.text())
435
436            source = self.cbSource.currentText()
437            mass = self.source_mass[str(source)]
438            self.resolution.set_neutron_mass(float(mass))
439
440            wavelength_spread = self._str2longlist(\
441                        self.txtWavelengthSpread.text().split(';')[0])
442            # Validate the wave inputs
443            wave_input = self._validate_q_input(wavelength, wavelength_spread)
444            if wave_input is not None:
445                wavelength, wavelength_spread = wave_input
446
447            self.resolution.set_wave(wavelength)
448            self.resolution.set_wave_spread(wavelength_spread)
449
450            # use legacy validator for correct input assignment
451
452            source_aperture_size = self.txtSourceApertureSize.text()
453            source_aperture_size = self._str2longlist(source_aperture_size)
454            self.resolution.set_source_aperture_size(source_aperture_size)
455
456            sample_aperture_size = self.txtSampleApertureSize.text()
457            sample_aperture_size = self._string2list(sample_aperture_size)
458            self.resolution.set_sample_aperture_size(sample_aperture_size)
459
460            source2sample_distance = self.txtSource2SampleDistance.text()
461            source2sample_distance = self._string2list(source2sample_distance)
462            self.resolution.set_source2sample_distance(source2sample_distance)
463
464            sample2sample_distance = self.txtSampleOffset.text()
465            sample2sample_distance = self._string2list(sample2sample_distance)
466            self.resolution.set_sample2sample_distance(sample2sample_distance)
467
468            sample2detector_distance = self.txtSample2DetectorDistance.text()
469            sample2detector_distance = self._string2list(
470                sample2detector_distance)
471            self.resolution.set_sample2detector_distance(
472                sample2detector_distance)
473
474            detector_size = self.txtDetectorSize.text()
475            detector_size = self._string2list(detector_size)
476            self.resolution.set_detector_size(detector_size)
477
478            detector_pix_size = self.txtDetectorPixSize.text()
479            detector_pix_size = self._string2list(detector_pix_size)
480            self.resolution.set_detector_pix_size(detector_pix_size)
481
482            self.qx = self._string2inputlist(self.txtQx.text())
483            self.qy = self._string2inputlist(self.txtQy.text())
484
485            # Find min max of qs
486            xmin = min(self.qx)
487            xmax = max(self.qx)
488            ymin = min(self.qy)
489            ymax = max(self.qy)
490            if not self._validate_q_input(self.qx, self.qy):
491                raise ValueError("Invalid Q input")
492        except:
493            msg = "An error occurred during the resolution computation."
494            msg += "Please check your inputs..."
495            logging.warning(msg)
496            return
497
498        # Validate the q inputs
499        q_input = self._validate_q_input(self.qx, self.qy)
500        if q_input is not None:
501            self.qx, self.qy = q_input
502
503        # Make list of q min max for mapping
504        for i in range(len(self.qx)):
505            qx_min.append(xmin)
506            qx_max.append(xmax)
507        for i in range(len(self.qy)):
508            qy_min.append(ymin)
509            qy_max.append(ymax)
510
511        # Compute the resolution
512        if self.image is not None:
513            self.resolution.reset_image()
514
515        # Compute and get the image plot
516        try:
517            cal_res = threads.deferToThread(self.map_wrapper,
518                                            self.calc_func,
519                                            self.qx,
520                                            self.qy,
521                                            qx_min,
522                                            qx_max,
523                                            qy_min, qy_max)
524
525            cal_res.addCallback(self.complete)
526            cal_res.addErrback(self.calculateFailed)
527
528            # logging.info("Computation is in progress...")
529            self.cmdCompute.setText('Wait...')
530            self.cmdCompute.setEnabled(False)
531        except:
532            raise
533
534    def calculateFailed(self, reason):
535        print("calculateFailed Failed with:\n", reason)
536        pass
537
538    def complete(self, image):
539        """
540        Complete computation
541        """
542        self.image = image
543
544        # Get and format the sigmas
545        sigma_r = self.formatNumber(self.resolution.sigma_1)
546        sigma_phi = self.formatNumber(self.resolution.sigma_2)
547        sigma_lamd = self.formatNumber(self.resolution.sigma_lamd)
548        sigma_1d = self.formatNumber(self.resolution.sigma_1d)
549
550        # Set output values
551        self.txtSigma_x.setText(str(sigma_r))
552        self.txtSigma_y.setText(str(sigma_phi))
553        self.txtSigma_lamd.setText(str(sigma_lamd))
554        self.txt1DSigma.setText(str(sigma_1d))
555
556        self.cmdCompute.setText('Compute')
557        self.cmdCompute.setEnabled(True)
558
559        self.new2DPlot()
560
561        return
562
563    def map_wrapper(self, func, qx, qy, qx_min, qx_max, qy_min, qy_max):
564        """
565        Prepare the Mapping for the computation
566        : params qx, qy, qx_min, qx_max, qy_min, qy_max:
567        : return: image (numpy array)
568        """
569        # This fails in py3 with
570        # [Failure instance: Traceback: <class 'TypeError'>: 'map' object is not subscriptable
571        # INVESTIGATE
572        image = map(func, qx, qy,
573                    qx_min, qx_max,
574                    qy_min, qy_max)[0]
575        return image
576
577    def calc_func(self, qx, qy, qx_min, qx_max, qy_min, qy_max):
578        """
579        Perform the calculation for a given set of Q values.
580        : return: image (numpy array)
581        """
582        try:
583            qx_value = float(qx)
584            qy_value = float(qy)
585        except :
586            raise ValueError
587
588        # calculate 2D resolution distribution image
589        image = self.resolution.compute_and_plot(qx_value, qy_value,
590                                                 qx_min, qx_max, qy_min,
591                                                 qy_max,
592                                                 self.det_coordinate)
593        return image
594
595    # #################################
596    # Legacy validators
597    # #################################
598    def _string2list(self, input_string):
599        """
600        Change NNN, NNN to list,ie. [NNN, NNN] where NNN is a number
601        """
602        new_numbers_list = []
603        # check the number of floats
604        try:
605            strg = float(input_string)
606            new_numbers_list.append(strg)
607        except:
608            string_split = input_string.split(',')
609            if len(string_split) == 1 or len(string_split) == 2:
610                new_numbers_list = [float(item) for item in string_split]
611            else:
612                msg = "The numbers must be one or two (separated by ',')"
613                logging.info(msg)
614                raise RuntimeError(msg)
615
616        return new_numbers_list
617
618    def _string2inputlist(self, input_string):
619        """
620        Change NNN, NNN,... to list,ie. [NNN, NNN,...] where NNN is a number
621        : return new_list: string like list
622        """
623        new_list = []
624        string_split = input_string.split(',')
625        try:
626            new_list = [float(t) for t in string_split]
627        except:
628            logging.error(sys.exc_info()[1])
629        return new_list
630
631    def _str2longlist(self, input_string):
632        """
633          Change NNN, NNN,... to list, NNN - NNN ; NNN to list, or float to list
634          : return new_string: string like list
635          """
636        try:
637            # is float
638            out = [float(input_string)]
639            return out
640        except:
641            if self.cbWaveColor.currentText() == 'Monochromatic':
642                logging.warning("Wrong format of inputs.")
643            else:
644                try:
645                    # has a '-'
646                    if input_string.count('-') > 0:
647                        value = input_string.split('-')
648                        if value[1].count(';') > 0:
649                            # has a ';'
650                            last_list = value[1].split(';')
651                            num = numpy.ceil(float(last_list[1]))
652                            max_value = float(last_list[0])
653                            self.num_wave = num
654                        else:
655                            # default num
656                            num = self.num_wave
657                            max_value = float(value[1])
658                        min_value = float(value[0])
659                        # make a list
660                        bin_size = numpy.fabs(max_value - min_value) / (num - 1)
661                        out = [min_value + bin_size * bnum for bnum in
662                               range(num)]
663                        return out
664                    if input_string.count(',') > 0:
665                        out = self._string2inputlist(input_string)
666                        return out
667                except:
668                    logging.error(sys.exc_info()[1])
669
670    def _validate_q_input(self, qx, qy):
671        """
672        Check if q inputs are valid
673        : params qx:  qx as a list
674        : params qy:  qy as a list
675        : return: True/False
676        """
677        # check qualifications
678        if qx.__class__.__name__ != 'list':
679            return None
680        if qy.__class__.__name__ != 'list':
681            return None
682        if len(qx) < 1:
683            return None
684        if len(qy) < 1:
685            return None
686        # allow one input
687        if len(qx) == 1 and len(qy) > 1:
688            qx = [qx[0] for ind in range(len(qy))]
689
690        if len(qy) == 1 and len(qx) > 1:
691            qy = [qy[0] for ind in range(len(qx))]
692        # check length
693        if len(qx) != len(qy):
694            return None
695        if qx is None or qy is None:
696            return None
697        return qx, qy
698
699    def formatNumber(self, value=None):
700        """
701        Return a float in a standardized, human-readable formatted string
702        """
703        try:
704            value = float(value)
705        except:
706            output = None
707            return output
708
709        output = "%-7.4g" % value
710        return output.lstrip().rstrip()
711
712    # #################################
713    # Plot
714    # #################################
715
716    def createTemplate2DPlot(self):
717        """
718        Create a template for 2D data
719        """
720        self.plotter = Plotter2DWidget(self, quickplot=True)
721        self.plotter.scale = 'linear'
722        self.plotter.cmap = None
723        layout = QtGui.QHBoxLayout()
724        layout.setContentsMargins(0, 0, 0, 0)
725        self.graphicsView.setLayout(layout)
726        layout.addWidget(self.plotter)
727
728    def new2DPlot(self):
729        """
730        Create a new 2D data instance based on computing results
731        """
732        qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
733
734        dx_size = (qx_max - qx_min) / (1000 - 1)
735        dy_size = (qy_max - qy_min) / (1000 - 1)
736        x_val = numpy.arange(qx_min, qx_max, dx_size)
737        y_val = numpy.arange(qy_max, qy_min, -dy_size)
738
739        if len(self.plotter.ax.patches):
740            self.plotter.ax.patches[0].remove()
741
742        self.drawLines()
743
744        self.plotter.data = Data2D(image=self.image,
745                      qx_data=x_val,
746                      qy_data=y_val,
747                      xmin=qx_min, xmax=qx_max,
748                      ymin=qy_min, ymax=qy_max)
749
750        self.plotter.plot()
751        self.plotter.show()
752
753    def drawLines(self):
754        """
755        Draw lines in image if applicable
756        """
757        wave_list, _ = self.resolution.get_wave_list()
758        if len(wave_list) > 1 and wave_list[-1] == max(wave_list):
759            color = 'g'
760            # draw a green rectangle(limit for the longest wavelength
761            # to be involved) for tof inputs
762            # Get the params from resolution
763            # plotting range for largest wavelength
764            qx_min = self.resolution.qx_min
765            qx_max = self.resolution.qx_max
766            qy_min = self.resolution.qy_min
767            qy_max = self.resolution.qy_max
768            # detector range
769            detector_qx_min = self.resolution.detector_qx_min
770            detector_qx_max = self.resolution.detector_qx_max
771            detector_qy_min = self.resolution.detector_qy_min
772            detector_qy_max = self.resolution.detector_qy_max
773
774            rect = patches.Rectangle((detector_qx_min + 0.0002,
775                                      detector_qy_min + 0.0002),
776                                     detector_qx_max - detector_qx_min,
777                                     detector_qy_max - detector_qy_min,
778                                     linewidth=2,
779                                     edgecolor=color, facecolor='none')
780            self.plotter.ax.add_patch(rect)
781        else:
782            qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
783            # detector range
784            detector_qx_min = self.resolution.qxmin_limit
785            detector_qx_max = self.resolution.qxmax_limit
786            detector_qy_min = self.resolution.qymin_limit
787            detector_qy_max = self.resolution.qymax_limit
788
789            xmin = min(self.qx)
790            xmax = max(self.qx)
791            ymin = min(self.qy)
792            ymax = max(self.qy)
793
794            if xmin < detector_qx_min or xmax > detector_qx_max or \
795                            ymin < detector_qy_min or ymax > detector_qy_max:
796                # message
797                msg = 'At least one q value located out side of\n'
798                msg += " the detector range (%s < qx < %s, %s < qy < %s),\n" % \
799                       (self.formatNumber(detector_qx_min),
800                        self.formatNumber(detector_qx_max),
801                        self.formatNumber(detector_qy_min),
802                        self.formatNumber(detector_qy_max))
803                msg += " is ignored in computation.\n"
804
805                logging.warning(msg)
806
807        # Draw zero axis lines.
808        if qy_min < 0 <= qy_max:
809            self.plotter.ax.axhline(linewidth=1)
810
811        if qx_min < 0 <= qx_max:
812            self.plotter.ax.axvline(linewidth=1)
Note: See TracBrowser for help on using the repository browser.