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

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 eae226b was e90988c, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Show help pages in default browser. Fixed some help links and modified unit tests. SASVIEW-800

  • 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            try:
330                basename = os.path.basename(datafile)
331                if basename not in list(self.spectrum_dic.keys()):
332                    self.cbCustomSpectrum.addItem(basename)
333
334                input_f = open(datafile, 'r')
335                buff = input_f.read()
336                lines = buff.split('\n')
337
338                wavelength = []
339                intensity = []
340
341                for line in lines:
342                    toks = line.split()
343                    try:
344                        wave = float(toks[0])
345                        intens = float(toks[1])
346                        wavelength.append(wave)
347                        intensity.append(intens)
348                    except:
349                        logging.info('Could not extract values from file')
350            except:
351                raise
352
353            self.spectrum_dic[basename] = [wavelength, intensity]
354            self.resolution.set_spectrum(self.spectrum_dic[basename])
355        return
356
357    # #################################
358    # Slots associated with signals from push buttons
359    # #################################
360
361    def onHelp(self):
362        """
363        Bring up the Resolution Calculator Documentation whenever
364        the HELP button is clicked.
365        Calls Documentation Window with the path of the location within the
366        documentation tree (after /doc/ ....".
367        """
368        location = "/user/sasgui/perspectives/calculator/resolution_calculator_help.html"
369        self.manager.showHelp(location)
370
371    def onReset(self):
372        # by default Spectrum label and cbCustomSpectrum are not visible
373        self.cbCustomSpectrum.setVisible(False)
374        self.lblSpectrum.setVisible(False)
375        # Comboboxes
376        self.cbCustomSpectrum.setCurrentIndex([self.cbCustomSpectrum.itemText(i)
377                                               for i in range(self.cbCustomSpectrum.count())].index('Flat'))
378        self.cbSource.setCurrentIndex([self.cbSource.itemText(i) for i in
379                                       range(self.cbSource.count())].index('Neutron'))
380        self.cbWaveColor.setCurrentIndex([self.cbWaveColor.itemText(i) for i
381                                          in range(self.cbWaveColor.count())].index('Monochromatic'))
382        # LineEdits
383        self.txtDetectorPixSize.setText('0.5, 0.5')
384        self.txtDetectorSize.setText('128, 128')
385        self.txtSample2DetectorDistance.setText('1000')
386        self.txtSampleApertureSize.setText('1.27')
387        self.txtSampleOffset.setText('0')
388        self.txtSource2SampleDistance.setText('1627')
389        self.txtSourceApertureSize.setText('3.81')
390        self.txtWavelength.setText('6.0')
391        self.txtWavelengthSpread.setText('0.125')
392        self.txtQx.setText('0.0')
393        self.txtQy.setText('0.0')
394        self.txt1DSigma.setText('0.0008289')
395        self.txtSigma_x.setText('0.0008288')
396        self.txtSigma_y.setText('0.0008288')
397        self.txtSigma_lamd.setText('3.168e-05')
398
399        self.image = None
400        self.source_mass = _SOURCE_MASS
401        self.det_coordinate = 'cartesian'
402        self.num_wave = 10
403        self.spectrum_dic = {}
404        self.spectrum_dic['Add new'] = ''
405        self.spectrum_dic['Flat'] = self.resolution.get_default_spectrum()
406        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
407        # Reset plot
408        self.onCompute()
409
410    # TODO Keep legacy validators??
411    def onCompute(self):
412        """
413        Execute the computation of resolution
414        """
415        # Q min max list default
416        qx_min = []
417        qx_max = []
418        qy_min = []
419        qy_max = []
420        # possible max qrange
421        self.resolution.qxmin_limit = 0
422        self.resolution.qxmax_limit = 0
423        self.resolution.qymin_limit = 0
424        self.resolution.qymax_limit = 0
425
426        try:
427            # Get all the values to compute
428            wavelength = self._str2longlist(self.txtWavelength.text())
429
430            source = self.cbSource.currentText()
431            mass = self.source_mass[str(source)]
432            self.resolution.set_neutron_mass(float(mass))
433
434            wavelength_spread = self._str2longlist(\
435                        self.txtWavelengthSpread.text().split(';')[0])
436            # Validate the wave inputs
437            wave_input = self._validate_q_input(wavelength, wavelength_spread)
438            if wave_input is not None:
439                wavelength, wavelength_spread = wave_input
440
441            self.resolution.set_wave(wavelength)
442            self.resolution.set_wave_spread(wavelength_spread)
443
444            # use legacy validator for correct input assignment
445
446            source_aperture_size = self.txtSourceApertureSize.text()
447            source_aperture_size = self._str2longlist(source_aperture_size)
448            self.resolution.set_source_aperture_size(source_aperture_size)
449
450            sample_aperture_size = self.txtSampleApertureSize.text()
451            sample_aperture_size = self._string2list(sample_aperture_size)
452            self.resolution.set_sample_aperture_size(sample_aperture_size)
453
454            source2sample_distance = self.txtSource2SampleDistance.text()
455            source2sample_distance = self._string2list(source2sample_distance)
456            self.resolution.set_source2sample_distance(source2sample_distance)
457
458            sample2sample_distance = self.txtSampleOffset.text()
459            sample2sample_distance = self._string2list(sample2sample_distance)
460            self.resolution.set_sample2sample_distance(sample2sample_distance)
461
462            sample2detector_distance = self.txtSample2DetectorDistance.text()
463            sample2detector_distance = self._string2list(
464                sample2detector_distance)
465            self.resolution.set_sample2detector_distance(
466                sample2detector_distance)
467
468            detector_size = self.txtDetectorSize.text()
469            detector_size = self._string2list(detector_size)
470            self.resolution.set_detector_size(detector_size)
471
472            detector_pix_size = self.txtDetectorPixSize.text()
473            detector_pix_size = self._string2list(detector_pix_size)
474            self.resolution.set_detector_pix_size(detector_pix_size)
475
476            self.qx = self._string2inputlist(self.txtQx.text())
477            self.qy = self._string2inputlist(self.txtQy.text())
478
479            # Find min max of qs
480            xmin = min(self.qx)
481            xmax = max(self.qx)
482            ymin = min(self.qy)
483            ymax = max(self.qy)
484            if not self._validate_q_input(self.qx, self.qy):
485                raise ValueError("Invalid Q input")
486        except:
487            msg = "An error occurred during the resolution computation."
488            msg += "Please check your inputs..."
489            logging.warning(msg)
490            return
491
492        # Validate the q inputs
493        q_input = self._validate_q_input(self.qx, self.qy)
494        if q_input is not None:
495            self.qx, self.qy = q_input
496
497        # Make list of q min max for mapping
498        for i in range(len(self.qx)):
499            qx_min.append(xmin)
500            qx_max.append(xmax)
501        for i in range(len(self.qy)):
502            qy_min.append(ymin)
503            qy_max.append(ymax)
504
505        # Compute the resolution
506        if self.image is not None:
507            self.resolution.reset_image()
508
509        # Compute and get the image plot
510        try:
511            cal_res = threads.deferToThread(self.map_wrapper,
512                                            self.calc_func,
513                                            self.qx,
514                                            self.qy,
515                                            qx_min,
516                                            qx_max,
517                                            qy_min, qy_max)
518
519            cal_res.addCallback(self.complete)
520            cal_res.addErrback(self.calculateFailed)
521
522            # logging.info("Computation is in progress...")
523            self.cmdCompute.setText('Wait...')
524            self.cmdCompute.setEnabled(False)
525        except:
526            raise
527
528    def calculateFailed(self, reason):
529        print("calculateFailed Failed with:\n", reason)
530        pass
531
532    def complete(self, image):
533        """
534        Complete computation
535        """
536        self.image = image
537
538        # Get and format the sigmas
539        sigma_r = self.formatNumber(self.resolution.sigma_1)
540        sigma_phi = self.formatNumber(self.resolution.sigma_2)
541        sigma_lamd = self.formatNumber(self.resolution.sigma_lamd)
542        sigma_1d = self.formatNumber(self.resolution.sigma_1d)
543
544        # Set output values
545        self.txtSigma_x.setText(str(sigma_r))
546        self.txtSigma_y.setText(str(sigma_phi))
547        self.txtSigma_lamd.setText(str(sigma_lamd))
548        self.txt1DSigma.setText(str(sigma_1d))
549
550        self.cmdCompute.setText('Compute')
551        self.cmdCompute.setEnabled(True)
552
553        self.new2DPlot()
554
555        return
556
557    def map_wrapper(self, func, qx, qy, qx_min, qx_max, qy_min, qy_max):
558        """
559        Prepare the Mapping for the computation
560        : params qx, qy, qx_min, qx_max, qy_min, qy_max:
561        : return: image (numpy array)
562        """
563        image = list(map(func, qx, qy,
564                    qx_min, qx_max,
565                    qy_min, qy_max))[0]
566
567        return image
568
569    def calc_func(self, qx, qy, qx_min, qx_max, qy_min, qy_max):
570        """
571        Perform the calculation for a given set of Q values.
572        : return: image (numpy array)
573        """
574        try:
575            qx_value = float(qx)
576            qy_value = float(qy)
577        except :
578            raise ValueError
579
580        # calculate 2D resolution distribution image
581        image = self.resolution.compute_and_plot(qx_value, qy_value,
582                                                 qx_min, qx_max, qy_min,
583                                                 qy_max,
584                                                 self.det_coordinate)
585        return image
586
587    # #################################
588    # Legacy validators
589    # #################################
590    def _string2list(self, input_string):
591        """
592        Change NNN, NNN to list,ie. [NNN, NNN] where NNN is a number
593        """
594        new_numbers_list = []
595        # check the number of floats
596        try:
597            strg = float(input_string)
598            new_numbers_list.append(strg)
599        except:
600            string_split = input_string.split(',')
601            if len(string_split) == 1 or len(string_split) == 2:
602                new_numbers_list = [float(item) for item in string_split]
603            else:
604                msg = "The numbers must be one or two (separated by ',')"
605                logging.info(msg)
606                raise RuntimeError(msg)
607
608        return new_numbers_list
609
610    def _string2inputlist(self, input_string):
611        """
612        Change NNN, NNN,... to list,ie. [NNN, NNN,...] where NNN is a number
613        : return new_list: string like list
614        """
615        new_list = []
616        string_split = input_string.split(',')
617        try:
618            new_list = [float(t) for t in string_split]
619        except:
620            logging.error(sys.exc_info()[1])
621        return new_list
622
623    def _str2longlist(self, input_string):
624        """
625          Change NNN, NNN,... to list, NNN - NNN ; NNN to list, or float to list
626          : return new_string: string like list
627          """
628        try:
629            # is float
630            out = [float(input_string)]
631            return out
632        except:
633            if self.cbWaveColor.currentText() == 'Monochromatic':
634                logging.warning("Wrong format of inputs.")
635            else:
636                try:
637                    # has a '-'
638                    if input_string.count('-') > 0:
639                        value = input_string.split('-')
640                        if value[1].count(';') > 0:
641                            # has a ';'
642                            last_list = value[1].split(';')
643                            num = numpy.ceil(float(last_list[1]))
644                            max_value = float(last_list[0])
645                            self.num_wave = num
646                        else:
647                            # default num
648                            num = self.num_wave
649                            max_value = float(value[1])
650                        min_value = float(value[0])
651                        # make a list
652                        bin_size = numpy.fabs(max_value - min_value) / (num - 1)
653                        out = [min_value + bin_size * bnum for bnum in
654                               range(num)]
655                        return out
656                    if input_string.count(',') > 0:
657                        out = self._string2inputlist(input_string)
658                        return out
659                except:
660                    logging.error(sys.exc_info()[1])
661
662    def _validate_q_input(self, qx, qy):
663        """
664        Check if q inputs are valid
665        : params qx:  qx as a list
666        : params qy:  qy as a list
667        : return: True/False
668        """
669        # check qualifications
670        if qx.__class__.__name__ != 'list':
671            return None
672        if qy.__class__.__name__ != 'list':
673            return None
674        if len(qx) < 1:
675            return None
676        if len(qy) < 1:
677            return None
678        # allow one input
679        if len(qx) == 1 and len(qy) > 1:
680            qx = [qx[0] for ind in range(len(qy))]
681
682        if len(qy) == 1 and len(qx) > 1:
683            qy = [qy[0] for ind in range(len(qx))]
684        # check length
685        if len(qx) != len(qy):
686            return None
687        if qx is None or qy is None:
688            return None
689        return qx, qy
690
691    def formatNumber(self, value=None):
692        """
693        Return a float in a standardized, human-readable formatted string
694        """
695        try:
696            value = float(value)
697        except:
698            output = None
699            return output
700
701        output = "%-7.4g" % value
702        return output.lstrip().rstrip()
703
704    # #################################
705    # Plot
706    # #################################
707
708    def createTemplate2DPlot(self):
709        """
710        Create a template for 2D data
711        """
712        self.plotter = Plotter2DWidget(self, quickplot=True)
713        self.plotter.scale = 'linear'
714        self.plotter.cmap = None
715        layout = QtWidgets.QHBoxLayout()
716        layout.setContentsMargins(0, 0, 0, 0)
717        self.graphicsView.setLayout(layout)
718        layout.addWidget(self.plotter)
719
720    def new2DPlot(self):
721        """
722        Create a new 2D data instance based on computing results
723        """
724        qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
725
726        dx_size = (qx_max - qx_min) / (1000 - 1)
727        dy_size = (qy_max - qy_min) / (1000 - 1)
728        x_val = numpy.arange(qx_min, qx_max, dx_size)
729        y_val = numpy.arange(qy_max, qy_min, -dy_size)
730
731        if len(self.plotter.ax.patches):
732            self.plotter.ax.patches[0].remove()
733
734        self.drawLines()
735
736        self.plotter.data = Data2D(image=self.image,
737                      qx_data=x_val,
738                      qy_data=y_val,
739                      xmin=qx_min, xmax=qx_max,
740                      ymin=qy_min, ymax=qy_max)
741
742        self.plotter.plot()
743        self.plotter.show()
744        self.plotter.update()
745
746    def drawLines(self):
747        """
748        Draw lines in image if applicable
749        """
750        wave_list, _ = self.resolution.get_wave_list()
751        if len(wave_list) > 1 and wave_list[-1] == max(wave_list):
752            color = 'g'
753            # draw a green rectangle(limit for the longest wavelength
754            # to be involved) for tof inputs
755            # Get the params from resolution
756            # plotting range for largest wavelength
757            qx_min = self.resolution.qx_min
758            qx_max = self.resolution.qx_max
759            qy_min = self.resolution.qy_min
760            qy_max = self.resolution.qy_max
761            # detector range
762            detector_qx_min = self.resolution.detector_qx_min
763            detector_qx_max = self.resolution.detector_qx_max
764            detector_qy_min = self.resolution.detector_qy_min
765            detector_qy_max = self.resolution.detector_qy_max
766
767            rect = patches.Rectangle((detector_qx_min + 0.0002,
768                                      detector_qy_min + 0.0002),
769                                     detector_qx_max - detector_qx_min,
770                                     detector_qy_max - detector_qy_min,
771                                     linewidth=2,
772                                     edgecolor=color, facecolor='none')
773            self.plotter.ax.add_patch(rect)
774        else:
775            qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
776            # detector range
777            detector_qx_min = self.resolution.qxmin_limit
778            detector_qx_max = self.resolution.qxmax_limit
779            detector_qy_min = self.resolution.qymin_limit
780            detector_qy_max = self.resolution.qymax_limit
781
782            xmin = min(self.qx)
783            xmax = max(self.qx)
784            ymin = min(self.qy)
785            ymax = max(self.qy)
786
787            if xmin < detector_qx_min or xmax > detector_qx_max or \
788                            ymin < detector_qy_min or ymax > detector_qy_max:
789                # message
790                msg = 'At least one q value located out side of\n'
791                msg += " the detector range (%s < qx < %s, %s < qy < %s),\n" % \
792                       (self.formatNumber(detector_qx_min),
793                        self.formatNumber(detector_qx_max),
794                        self.formatNumber(detector_qy_min),
795                        self.formatNumber(detector_qy_max))
796                msg += " is ignored in computation.\n"
797
798                logging.warning(msg)
799
800        # Draw zero axis lines.
801        if qy_min < 0 <= qy_max:
802            self.plotter.ax.axhline(linewidth=1)
803
804        if qx_min < 0 <= qx_max:
805            self.plotter.ax.axvline(linewidth=1)
Note: See TracBrowser for help on using the repository browser.