source: sasview/src/sas/qtgui/Calculators/ResolutionCalculatorPanel.py @ 93c813f

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 93c813f was 93c813f, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

float→int cast must be made in GUI. SASVIEW-1137

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