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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since 33c0561 was 33c0561, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Replace Apply button menu driven functionality with additional button.
Removed Cancel.
Removed the window system context help button from all affected widgets.
SASVIEW-1239

  • Property mode set to 100644
File size: 30.8 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        # disable the context help icon
44        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
45
46        self.manager = parent
47
48        # New font to display angstrom symbol
49        new_font = 'font-family: -apple-system, "Helvetica Neue", "Ubuntu";'
50        self.lblUnitWavelength.setStyleSheet(new_font)
51        self.lblUnitQx.setStyleSheet(new_font)
52        self.lblUnitQy.setStyleSheet(new_font)
53        self.lblUnitSigmax.setStyleSheet(new_font)
54        self.lblUnitSigmay.setStyleSheet(new_font)
55        self.lblUnitSigmalamd.setStyleSheet(new_font)
56        self.lblUnit1DSigma.setStyleSheet(new_font)
57
58        # by default Spectrum label and cbCustomSpectrum are not visible
59        self.cbCustomSpectrum.setVisible(False)
60        self.lblSpectrum.setVisible(False)
61        # self.onReset()
62
63        # change index of comboboxes
64        self.cbWaveColor.currentIndexChanged.connect(self.onSelectWaveColor)
65        self.cbCustomSpectrum.currentIndexChanged.connect(self.onSelectCustomSpectrum)
66
67        # push buttons
68        self.cmdClose.clicked.connect(self.accept)
69        self.cmdHelp.clicked.connect(self.onHelp)
70        self.cmdCompute.clicked.connect(self.onCompute)
71        self.cmdReset.clicked.connect(self.onReset)
72
73        # input defaults
74        self.qx = []
75        self.qy = []
76        # dQ defaults
77        self.sigma_r = None
78        self.sigma_phi = None
79        self.sigma_1d = None
80
81        # number of bins for wavelength and wavelength spread
82        self.num_wave = 10
83        self.spectrum_dic = {}
84
85        # dQ 2d image
86        self.image = None
87        # Source selection dic
88        self.source_mass = _SOURCE_MASS
89        # detector coordinate of estimation of sigmas
90        self.det_coordinate = 'cartesian'
91
92        self.resolution = ResolutionCalculator()
93        self.spectrum_dic['Add new'] = ''
94        self.spectrum_dic['Flat'] = self.resolution.get_default_spectrum()
95        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
96
97        # validators
98        self.txtWavelength.editingFinished.connect(self.checkWavelength)
99        self.txtWavelengthSpread.editingFinished.connect(self.checkWavelengthSpread)
100
101        self.txtDetectorPixSize.editingFinished.connect(self.checkPixels)
102        self.txtDetectorSize.editingFinished.connect(self.checkPixels)
103
104        self.txtSourceApertureSize.editingFinished.connect(self.checkAperture)
105        self.txtSampleApertureSize.editingFinished.connect(self.checkAperture)
106
107        self.txtQx.editingFinished.connect(self.checkQx_y)
108        self.txtQy.editingFinished.connect(self.checkQx_y)
109
110        # double validator
111        self.txtSource2SampleDistance.setValidator(GuiUtils.DoubleValidator())
112        self.txtSample2DetectorDistance.setValidator(GuiUtils.DoubleValidator())
113        self.txtSampleOffset.setValidator(GuiUtils.DoubleValidator())
114
115        # call compute to calculate with default values
116        self.createTemplate2DPlot()
117        #self.onCompute()
118
119    # #################################
120    # Validators: red background in line edits when wrong input
121    # and display of info logging message
122    # #################################
123
124    def checkWavelength(self):
125        """ Validator for Wavelength
126         if TOF, wavelength = min - max else only one number """
127        text_edit = self.txtWavelength  # self.sender()
128        if text_edit.isModified():
129            text_edit.setStyleSheet(BG_WHITE)
130            input_string = str(text_edit.text())
131            if self.cbWaveColor.currentText() != 'TOF':
132                input_wavelength = re.match('\d+\.?\d*', input_string)
133                if input_wavelength is None:
134                    text_edit.setStyleSheet(BG_RED)
135                    self.cmdCompute.setEnabled(False)
136                    logging.info('Wavelength has to be a number.')
137                else:
138                    text_edit.setStyleSheet(BG_WHITE)
139                    self.cmdCompute.setEnabled(True)
140            else:
141                interval_wavelength = re.match('^\d+\.?\d*\s*-\s*\d+\.?\d*$',
142                                               input_string)
143
144                if interval_wavelength is None:
145                    text_edit.setStyleSheet(BG_RED)
146                    self.cmdCompute.setEnabled(False)
147                    logging.info("Wavelength's input has to be an interval: "
148                                 "min - max.")
149                else:
150                    # check on min < max
151                    [wavelength_min, wavelength_max] = \
152                        re.findall('\d+\.?\d*', interval_wavelength.group())
153
154                    if float(wavelength_min) >= float(wavelength_max):
155                        text_edit.setStyleSheet(BG_RED)
156                        self.cmdCompute.setEnabled(False)
157                        logging.info("Wavelength: min must be smaller than max.")
158
159                    else:
160                        text_edit.setStyleSheet(BG_WHITE)
161                        self.cmdCompute.setEnabled(True)
162
163    def checkWavelengthSpread(self):
164        """ Validator for WavelengthSpread
165         Input can be a 'number or min - max (; Number of bins)' """
166        text_edit = self.sender()
167
168        if text_edit.isModified():
169            text_edit.setStyleSheet(BG_WHITE)
170            if self.cbWaveColor.currentText() != 'TOF':
171                pattern = '^\d+\.?\d*(|;\s*\d+)$'
172                input_string = str(text_edit.text())
173                wavelength_spread_input = re.match(pattern, input_string)
174
175                if wavelength_spread_input is None:
176                    text_edit.setStyleSheet(BG_RED)
177                    self.cmdCompute.setEnabled(False)
178                    logging.info('Wavelength spread has to be specified: '
179                                 'single value or value; integer number of bins.')
180
181                else:
182                    split_input = wavelength_spread_input.group().split(';')
183                    self.num_wave = split_input[1] if len(split_input) > 1 else 10
184                    text_edit.setStyleSheet(BG_WHITE)
185                    self.cmdCompute.setEnabled(True)
186            else:
187                pattern = '^\d+\.?\d*\s*-\s*\d+\.?\d*(|;\s*\d+)$'
188                input_string = str(text_edit.text())
189                wavelength_spread_input = re.match(pattern, input_string)
190
191                if wavelength_spread_input is None:
192                    text_edit.setStyleSheet(BG_RED)
193                    self.cmdCompute.setEnabled(False)
194                    logging.info("Wavelength spread has to be specified: "
195                                 "doublet separated by '-' with optional "
196                                 "number of bins (given after ';'). "
197                                 "For example, 0.1 - 0.1 (; 20).")
198
199                else:
200                    split_input = wavelength_spread_input.group().split(';')
201                    self.num_wave = split_input[1] if len(
202                        split_input) > 1 else 10
203                    text_edit.setStyleSheet(BG_WHITE)
204                    self.cmdCompute.setEnabled(True)
205
206    def checkPixels(self):
207        """ Validator for detector pixel size and number """
208        text_edit = self.sender()
209
210        if text_edit.isModified():
211            text_edit.setStyleSheet(BG_WHITE)
212            pattern = '^\d+\.?\d*,\s*\d+\.?\d*$'
213            input_string = str(text_edit.text())
214            pixels_input = re.match(pattern, input_string)
215
216            if pixels_input is None:
217                text_edit.setStyleSheet(BG_RED)
218                self.cmdCompute.setEnabled(False)
219                logging.info('The input for the detector should contain 2 '
220                             'values separated by a comma.')
221
222            else:
223                text_edit.setStyleSheet(BG_WHITE)
224                self.cmdCompute.setEnabled(True)
225
226    def checkQx_y(self):
227        """ Validator for qx and qy inputs """
228        Q_modified = [self.txtQx.isModified(), self.txtQy.isModified()]
229        if any(Q_modified):
230            pattern = '^-?\d+\.?\d*(,\s*-?\d+\.?\d*)*$'
231            text_edit = self.txtQx if Q_modified[0] else self.txtQy
232            input_string = str(text_edit.text())
233            q_input = re.match(pattern, input_string)
234            if q_input is None:
235                text_edit.setStyleSheet(BG_RED)
236                self.cmdCompute.setEnabled(False)
237                logging.info('Qx and Qy should contain one or more comma-separated numbers.')
238            else:
239                text_edit.setStyleSheet(BG_WHITE)
240                self.cmdCompute.setEnabled(True)
241                qx = str(self.txtQx.text()).split(',')
242                qy = str(self.txtQy.text()).split(',')
243
244                if len(qx) == 1 and len(qy) > 1:
245                    fill_qx = ', '.join([qx[0]] * len(qy))
246                    self.txtQx.setText(fill_qx)
247
248                elif len(qy) == 1 and len(qx) > 1:
249                    fill_qy = ', '.join([qy[0]] * len(qx))
250                    self.txtQy.setText(fill_qy)
251
252                elif len(qx) != len(qy):
253                    text_edit.setStyleSheet(BG_RED)
254                    self.cmdCompute.setEnabled(False)
255                    logging.info(
256                        'Qx and Qy should have the same number of elements.')
257
258                else:
259                    text_edit.setStyleSheet(BG_WHITE)
260                    self.cmdCompute.setEnabled(True)
261
262    def checkAperture(self):
263        """ Validator for Sample and Source apertures"""
264        text_edit = self.sender()
265
266        if text_edit.isModified():
267            text_edit.setStyleSheet(BG_WHITE)
268            input_string = str(text_edit.text())
269            pattern = '^\d+\.?\d*(|,\s*\d+)$'
270            aperture_input = re.match(pattern, input_string)
271
272            if aperture_input is None:
273                text_edit.setStyleSheet(BG_RED)
274                self.cmdCompute.setEnabled(False)
275                logging.info('A circular aperture is defined by a single '
276                             'value (diameter). A rectangular aperture is '
277                             'defined by 2 values separated by a comma.')
278
279            else:
280                text_edit.setStyleSheet(BG_WHITE)
281                self.cmdCompute.setEnabled(True)
282
283    # #################################
284    # Slots associated with signals from comboboxes
285    # #################################
286
287    def onSelectWaveColor(self):
288        """ Modify layout of GUI when TOF selected: add elements
289        and modify default entry of Wavelength """
290        list_wdata = self.resolution.get_wave_list()
291        min_lambda = min(list_wdata[0])
292
293        min_wspread = min(list_wdata[1])
294        max_wspread = max(list_wdata[1])
295
296        if self.cbWaveColor.currentText() == 'TOF':
297            self.cbCustomSpectrum.setVisible(True)
298            self.lblSpectrum.setVisible(True)
299            # Get information about wavelength and spread
300
301            if len(list_wdata[0]) < 2:
302                max_lambda = 2 * min_lambda
303            else:
304                max_lambda = max(list_wdata[0])
305            self.txtWavelength.setText('{} - {}'.format(min_lambda, max_lambda))
306            self.txtWavelengthSpread.setText('{} - {}'.format(min_wspread,
307                                                    max_wspread))
308
309        else:
310            self.cbCustomSpectrum.setVisible(False)
311            self.lblSpectrum.setVisible(False)
312            # modify Wavelength line edit only if set for TOF (2 elements)
313
314            if len(self.txtWavelength.text().split('-')) >= 2:
315                self.txtWavelength.setText(str(min_lambda))
316                self.txtWavelengthSpread.setText(str(min_wspread))
317
318    def onSelectCustomSpectrum(self):
319        """ On Spectrum Combobox event"""
320        if self.cbCustomSpectrum.currentText() == 'Add New':
321            datafile = QtWidgets.QFileDialog.getOpenFileName(
322                self, "Choose a spectral distribution file","",
323                "All files (*.*)", None,
324                QtWidgets.QFileDialog.DontUseNativeDialog)[0]
325
326            if datafile is None or str(datafile) == '':
327                logging.info("No spectral distribution data chosen.")
328                self.cbCustomSpectrum.setCurrentIndex(0)
329                self.resolution.set_spectrum(self.spectrum_dic['Flat'])
330                return
331
332            basename = os.path.basename(datafile)
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            if wavelength and intensity:
351                if basename not in list(self.spectrum_dic.keys()):
352                    self.cbCustomSpectrum.addItem(basename)
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/qtgui/Calculators/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            det_size = self._string2list(detector_size)
470            # detector sizes must be ints. recast.
471            detector_size = [int(i) for i in det_size]
472            self.resolution.set_detector_size(detector_size)
473
474            detector_pix_size = self.txtDetectorPixSize.text()
475            detector_pix_size = self._string2list(detector_pix_size)
476            self.resolution.set_detector_pix_size(detector_pix_size)
477
478            self.qx = self._string2inputlist(self.txtQx.text())
479            self.qy = self._string2inputlist(self.txtQy.text())
480
481            # Find min max of qs
482            xmin = min(self.qx)
483            xmax = max(self.qx)
484            ymin = min(self.qy)
485            ymax = max(self.qy)
486            if not self._validate_q_input(self.qx, self.qy):
487                raise ValueError("Invalid Q input")
488        except:
489            msg = "An error occurred during the resolution computation."
490            msg += "Please check your inputs..."
491            logging.warning(msg)
492            return
493
494        # Validate the q inputs
495        q_input = self._validate_q_input(self.qx, self.qy)
496        if q_input is not None:
497            self.qx, self.qy = q_input
498
499        # Make list of q min max for mapping
500        for i in range(len(self.qx)):
501            qx_min.append(xmin)
502            qx_max.append(xmax)
503        for i in range(len(self.qy)):
504            qy_min.append(ymin)
505            qy_max.append(ymax)
506
507        # Compute the resolution
508        if self.image is not None:
509            self.resolution.reset_image()
510
511        # Compute and get the image plot
512        try:
513            cal_res = threads.deferToThread(self.map_wrapper,
514                                            self.calc_func,
515                                            self.qx,
516                                            self.qy,
517                                            qx_min,
518                                            qx_max,
519                                            qy_min, qy_max)
520
521            cal_res.addCallback(self.complete)
522            cal_res.addErrback(self.calculateFailed)
523
524            self.cmdCompute.setText('Wait...')
525            self.cmdCompute.setEnabled(False)
526        except:
527            raise
528
529    def calculateFailed(self, reason):
530        self.cmdCompute.setText('Compute')
531        self.cmdCompute.setEnabled(True)
532        logging.error(str(reason))
533
534    def complete(self, image):
535        """
536        Complete computation
537        """
538        self.image = image
539
540        # Get and format the sigmas
541        sigma_r = self.formatNumber(self.resolution.sigma_1)
542        sigma_phi = self.formatNumber(self.resolution.sigma_2)
543        sigma_lamd = self.formatNumber(self.resolution.sigma_lamd)
544        sigma_1d = self.formatNumber(self.resolution.sigma_1d)
545
546        # Set output values
547        self.txtSigma_x.setText(str(sigma_r))
548        self.txtSigma_y.setText(str(sigma_phi))
549        self.txtSigma_lamd.setText(str(sigma_lamd))
550        self.txt1DSigma.setText(str(sigma_1d))
551
552        self.cmdCompute.setText('Compute')
553        self.cmdCompute.setEnabled(True)
554
555        self.new2DPlot()
556
557        return
558
559    def map_wrapper(self, func, qx, qy, qx_min, qx_max, qy_min, qy_max):
560        """
561        Prepare the Mapping for the computation
562        : params qx, qy, qx_min, qx_max, qy_min, qy_max:
563        : return: image (numpy array)
564        """
565        image = list(map(func, qx, qy,
566                    qx_min, qx_max,
567                    qy_min, qy_max))[0]
568
569        return image
570
571    def calc_func(self, qx, qy, qx_min, qx_max, qy_min, qy_max):
572        """
573        Perform the calculation for a given set of Q values.
574        : return: image (numpy array)
575        """
576        try:
577            qx_value = float(qx)
578            qy_value = float(qy)
579        except :
580            raise ValueError
581
582        # calculate 2D resolution distribution image
583        image = self.resolution.compute_and_plot(qx_value, qy_value,
584                                                 qx_min, qx_max, qy_min,
585                                                 qy_max,
586                                                 self.det_coordinate)
587        return image
588
589    # #################################
590    # Legacy validators
591    # #################################
592    def _string2list(self, input_string):
593        """
594        Change NNN, NNN to list,ie. [NNN, NNN] where NNN is a number
595        """
596        new_numbers_list = []
597        # check the number of floats
598        try:
599            strg = float(input_string)
600            new_numbers_list.append(strg)
601        except:
602            string_split = input_string.split(',')
603            if len(string_split) == 1 or len(string_split) == 2:
604                new_numbers_list = [float(item) for item in string_split]
605            else:
606                msg = "The numbers must be one or two (separated by ',')"
607                logging.info(msg)
608                raise RuntimeError(msg)
609
610        return new_numbers_list
611
612    def _string2inputlist(self, input_string):
613        """
614        Change NNN, NNN,... to list,ie. [NNN, NNN,...] where NNN is a number
615        : return new_list: string like list
616        """
617        new_list = []
618        string_split = input_string.split(',')
619        try:
620            new_list = [float(t) for t in string_split]
621        except:
622            logging.error(sys.exc_info()[1])
623        return new_list
624
625    def _str2longlist(self, input_string):
626        """
627          Change NNN, NNN,... to list, NNN - NNN ; NNN to list, or float to list
628          : return new_string: string like list
629          """
630        try:
631            # is float
632            out = [float(input_string)]
633            return out
634        except:
635            if self.cbWaveColor.currentText() == 'Monochromatic':
636                logging.warning("Wrong format of inputs.")
637            else:
638                try:
639                    # has a '-'
640                    if input_string.count('-') > 0:
641                        value = input_string.split('-')
642                        if value[1].count(';') > 0:
643                            # has a ';'
644                            last_list = value[1].split(';')
645                            num = numpy.ceil(float(last_list[1]))
646                            max_value = float(last_list[0])
647                            self.num_wave = num
648                        else:
649                            # default num
650                            num = self.num_wave
651                            max_value = float(value[1])
652                        min_value = float(value[0])
653                        # make a list
654                        bin_size = numpy.fabs(max_value - min_value) / (num - 1)
655                        out = [min_value + bin_size * bnum for bnum in
656                               range(num)]
657                        return out
658                    if input_string.count(',') > 0:
659                        out = self._string2inputlist(input_string)
660                        return out
661                except:
662                    logging.error(sys.exc_info()[1])
663
664    def _validate_q_input(self, qx, qy):
665        """
666        Check if q inputs are valid
667        : params qx:  qx as a list
668        : params qy:  qy as a list
669        : return: True/False
670        """
671        # check qualifications
672        if qx.__class__.__name__ != 'list':
673            return None
674        if qy.__class__.__name__ != 'list':
675            return None
676        if len(qx) < 1:
677            return None
678        if len(qy) < 1:
679            return None
680        # allow one input
681        if len(qx) == 1 and len(qy) > 1:
682            qx = [qx[0] for ind in range(len(qy))]
683
684        if len(qy) == 1 and len(qx) > 1:
685            qy = [qy[0] for ind in range(len(qx))]
686        # check length
687        if len(qx) != len(qy):
688            return None
689        if qx is None or qy is None:
690            return None
691        return qx, qy
692
693    def formatNumber(self, value=None):
694        """
695        Return a float in a standardized, human-readable formatted string
696        """
697        try:
698            value = float(value)
699        except:
700            output = None
701            return output
702
703        output = "%-7.4g" % value
704        return output.lstrip().rstrip()
705
706    # #################################
707    # Plot
708    # #################################
709
710    def createTemplate2DPlot(self):
711        """
712        Create a template for 2D data
713        """
714        self.plotter = Plotter2DWidget(self, manager=self.manager, quickplot=True)
715        self.plotter.scale = 'linear'
716        self.plotter.cmap = None
717        layout = QtWidgets.QHBoxLayout()
718        layout.setContentsMargins(0, 0, 0, 0)
719        self.graphicsView.setLayout(layout)
720        layout.addWidget(self.plotter)
721
722    def new2DPlot(self):
723        """
724        Create a new 2D data instance based on computing results
725        """
726        qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
727
728        dx_size = (qx_max - qx_min) / (1000 - 1)
729        dy_size = (qy_max - qy_min) / (1000 - 1)
730        x_val = numpy.arange(qx_min, qx_max, dx_size)
731        y_val = numpy.arange(qy_max, qy_min, -dy_size)
732
733        if len(self.plotter.ax.patches):
734            self.plotter.ax.patches[0].remove()
735
736        self.drawLines()
737
738        self.plotter.data = Data2D(image=self.image,
739                      qx_data=x_val,
740                      qy_data=y_val,
741                      xmin=qx_min, xmax=qx_max,
742                      ymin=qy_min, ymax=qy_max)
743
744        self.plotter.plot()
745        self.plotter.show()
746        self.plotter.update()
747
748    def drawLines(self):
749        """
750        Draw lines in image if applicable
751        """
752        wave_list, _ = self.resolution.get_wave_list()
753        if len(wave_list) > 1 and wave_list[-1] == max(wave_list):
754            color = 'g'
755            # draw a green rectangle(limit for the longest wavelength
756            # to be involved) for tof inputs
757            # Get the params from resolution
758            # plotting range for largest wavelength
759            qx_min = self.resolution.qx_min
760            qx_max = self.resolution.qx_max
761            qy_min = self.resolution.qy_min
762            qy_max = self.resolution.qy_max
763            # detector range
764            detector_qx_min = self.resolution.detector_qx_min
765            detector_qx_max = self.resolution.detector_qx_max
766            detector_qy_min = self.resolution.detector_qy_min
767            detector_qy_max = self.resolution.detector_qy_max
768
769            rect = patches.Rectangle((detector_qx_min + 0.0002,
770                                      detector_qy_min + 0.0002),
771                                     detector_qx_max - detector_qx_min,
772                                     detector_qy_max - detector_qy_min,
773                                     linewidth=2,
774                                     edgecolor=color, facecolor='none')
775            self.plotter.ax.add_patch(rect)
776        else:
777            qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
778            # detector range
779            detector_qx_min = self.resolution.qxmin_limit
780            detector_qx_max = self.resolution.qxmax_limit
781            detector_qy_min = self.resolution.qymin_limit
782            detector_qy_max = self.resolution.qymax_limit
783
784            xmin = min(self.qx)
785            xmax = max(self.qx)
786            ymin = min(self.qy)
787            ymax = max(self.qy)
788
789            if xmin < detector_qx_min or xmax > detector_qx_max or \
790                            ymin < detector_qy_min or ymax > detector_qy_max:
791                # message
792                msg = 'At least one q value located out side of\n'
793                msg += " the detector range (%s < qx < %s, %s < qy < %s),\n" % \
794                       (self.formatNumber(detector_qx_min),
795                        self.formatNumber(detector_qx_max),
796                        self.formatNumber(detector_qy_min),
797                        self.formatNumber(detector_qy_max))
798                msg += " is ignored in computation.\n"
799
800                logging.warning(msg)
801
802        # Draw zero axis lines.
803        if qy_min < 0 <= qy_max:
804            self.plotter.ax.axhline(linewidth=1)
805
806        if qx_min < 0 <= qx_max:
807            self.plotter.ax.axvline(linewidth=1)
Note: See TracBrowser for help on using the repository browser.