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

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 7969b9c was 4992ff2, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial, in-progress version. Not really working atm. SASVIEW-787

  • Property mode set to 100644
File size: 31.0 KB
RevLine 
[01cda57]1"""
2This object is a small tool to allow user to quickly
3determine the variance in q  from the
4instrumental parameters.
5"""
[4992ff2]6from PyQt5 import QtCore
7from PyQt5 import QtGui
8from PyQt5 import QtWidgets
[01cda57]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
[b3e8629]23from .UI.ResolutionCalculatorPanelUI import Ui_ResolutionCalculatorPanel
[01cda57]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
[4992ff2]36class ResolutionCalculatorPanel(QtWidgets.QDialog, Ui_ResolutionCalculatorPanel):
[01cda57]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(QtGui.QDoubleValidator())
109        self.txtSample2DetectorDistance.setValidator(QtGui.QDoubleValidator())
110        self.txtSampleOffset.setValidator(QtGui.QDoubleValidator())
111
112        # call compute to calculate with default values
113        self.createTemplate2DPlot()
[7fb471d]114        #self.onCompute()
[01cda57]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():
[7fb471d]126            text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]131                    text_edit.setStyleSheet(BG_RED)
[01cda57]132                    self.cmdCompute.setEnabled(False)
133                    logging.info('Wavelength has to be a number.')
134                else:
[7fb471d]135                    text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]142                    text_edit.setStyleSheet(BG_RED)
[01cda57]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):
[7fb471d]152                        text_edit.setStyleSheet(BG_RED)
[01cda57]153                        self.cmdCompute.setEnabled(False)
154                        logging.info("Wavelength: min must be smaller than max.")
155
156                    else:
[7fb471d]157                        text_edit.setStyleSheet(BG_WHITE)
[01cda57]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():
[7fb471d]166            text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]173                    text_edit.setStyleSheet(BG_RED)
[01cda57]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
[7fb471d]181                    text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]189                    text_edit.setStyleSheet(BG_RED)
[01cda57]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
[7fb471d]200                    text_edit.setStyleSheet(BG_WHITE)
[01cda57]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():
[7fb471d]208            text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]214                text_edit.setStyleSheet(BG_RED)
[01cda57]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:
[7fb471d]220                text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]232                text_edit.setStyleSheet(BG_RED)
[01cda57]233                self.cmdCompute.setEnabled(False)
[fc4fec8]234                logging.info('Qx and Qy should contain one or more comma-separated numbers.')
[01cda57]235            else:
[7fb471d]236                text_edit.setStyleSheet(BG_WHITE)
[01cda57]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):
[7fb471d]250                    text_edit.setStyleSheet(BG_RED)
[01cda57]251                    self.cmdCompute.setEnabled(False)
252                    logging.info(
[fc4fec8]253                        'Qx and Qy should have the same number of elements.')
[01cda57]254
255                else:
[7fb471d]256                    text_edit.setStyleSheet(BG_WHITE)
[01cda57]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():
[7fb471d]264            text_edit.setStyleSheet(BG_WHITE)
[01cda57]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:
[7fb471d]270                text_edit.setStyleSheet(BG_RED)
[01cda57]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:
[7fb471d]277                text_edit.setStyleSheet(BG_WHITE)
[01cda57]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':
[4992ff2]318            datafile = QtWidgets.QFileDialog.getOpenFileName(
[01cda57]319                self, "Choose a spectral distribution file", "",
320                "All files (*.*)",
[4992ff2]321                QtWidgets.QFileDialog.DontUseNativeDialog)
[01cda57]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)
[b3e8629]331                if basename not in list(self.spectrum_dic.keys()):
[01cda57]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        try:
369            location = GuiUtils.HELP_DIRECTORY_LOCATION + \
370                       "/user/sasgui/perspectives/calculator/resolution_calculator_help.html"
371            self.manager._helpView.load(QtCore.QUrl(location))
372            self.manager._helpView.show()
373
374        except AttributeError:
375            # No manager defined - testing and standalone runs
376            pass
377
378    def onReset(self):
379        # by default Spectrum label and cbCustomSpectrum are not visible
380        self.cbCustomSpectrum.setVisible(False)
381        self.lblSpectrum.setVisible(False)
382        # Comboboxes
383        self.cbCustomSpectrum.setCurrentIndex([self.cbCustomSpectrum.itemText(i)
384                                               for i in range(self.cbCustomSpectrum.count())].index('Flat'))
385        self.cbSource.setCurrentIndex([self.cbSource.itemText(i) for i in
386                                       range(self.cbSource.count())].index('Neutron'))
387        self.cbWaveColor.setCurrentIndex([self.cbWaveColor.itemText(i) for i
388                                          in range(self.cbWaveColor.count())].index('Monochromatic'))
389        # LineEdits
390        self.txtDetectorPixSize.setText('0.5, 0.5')
391        self.txtDetectorSize.setText('128, 128')
392        self.txtSample2DetectorDistance.setText('1000')
393        self.txtSampleApertureSize.setText('1.27')
394        self.txtSampleOffset.setText('0')
395        self.txtSource2SampleDistance.setText('1627')
396        self.txtSourceApertureSize.setText('3.81')
397        self.txtWavelength.setText('6.0')
398        self.txtWavelengthSpread.setText('0.125')
399        self.txtQx.setText('0.0')
400        self.txtQy.setText('0.0')
401        self.txt1DSigma.setText('0.0008289')
402        self.txtSigma_x.setText('0.0008288')
403        self.txtSigma_y.setText('0.0008288')
404        self.txtSigma_lamd.setText('3.168e-05')
405
406        self.image = None
407        self.source_mass = _SOURCE_MASS
408        self.det_coordinate = 'cartesian'
409        self.num_wave = 10
410        self.spectrum_dic = {}
411        self.spectrum_dic['Add new'] = ''
412        self.spectrum_dic['Flat'] = self.resolution.get_default_spectrum()
413        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
414        # Reset plot
415        self.onCompute()
416
417    # TODO Keep legacy validators??
418    def onCompute(self):
419        """
420        Execute the computation of resolution
421        """
422        # Q min max list default
423        qx_min = []
424        qx_max = []
425        qy_min = []
426        qy_max = []
427        # possible max qrange
428        self.resolution.qxmin_limit = 0
429        self.resolution.qxmax_limit = 0
430        self.resolution.qymin_limit = 0
431        self.resolution.qymax_limit = 0
432
433        try:
434            # Get all the values to compute
435            wavelength = self._str2longlist(self.txtWavelength.text())
436
437            source = self.cbSource.currentText()
438            mass = self.source_mass[str(source)]
439            self.resolution.set_neutron_mass(float(mass))
440
441            wavelength_spread = self._str2longlist(\
442                        self.txtWavelengthSpread.text().split(';')[0])
443            # Validate the wave inputs
444            wave_input = self._validate_q_input(wavelength, wavelength_spread)
445            if wave_input is not None:
446                wavelength, wavelength_spread = wave_input
447
448            self.resolution.set_wave(wavelength)
449            self.resolution.set_wave_spread(wavelength_spread)
450
451            # use legacy validator for correct input assignment
452
453            source_aperture_size = self.txtSourceApertureSize.text()
454            source_aperture_size = self._str2longlist(source_aperture_size)
455            self.resolution.set_source_aperture_size(source_aperture_size)
456
457            sample_aperture_size = self.txtSampleApertureSize.text()
458            sample_aperture_size = self._string2list(sample_aperture_size)
459            self.resolution.set_sample_aperture_size(sample_aperture_size)
460
461            source2sample_distance = self.txtSource2SampleDistance.text()
462            source2sample_distance = self._string2list(source2sample_distance)
463            self.resolution.set_source2sample_distance(source2sample_distance)
464
465            sample2sample_distance = self.txtSampleOffset.text()
466            sample2sample_distance = self._string2list(sample2sample_distance)
467            self.resolution.set_sample2sample_distance(sample2sample_distance)
468
469            sample2detector_distance = self.txtSample2DetectorDistance.text()
470            sample2detector_distance = self._string2list(
471                sample2detector_distance)
472            self.resolution.set_sample2detector_distance(
473                sample2detector_distance)
474
475            detector_size = self.txtDetectorSize.text()
476            detector_size = self._string2list(detector_size)
477            self.resolution.set_detector_size(detector_size)
478
479            detector_pix_size = self.txtDetectorPixSize.text()
480            detector_pix_size = self._string2list(detector_pix_size)
481            self.resolution.set_detector_pix_size(detector_pix_size)
482
483            self.qx = self._string2inputlist(self.txtQx.text())
484            self.qy = self._string2inputlist(self.txtQy.text())
485
486            # Find min max of qs
487            xmin = min(self.qx)
488            xmax = max(self.qx)
489            ymin = min(self.qy)
490            ymax = max(self.qy)
491            if not self._validate_q_input(self.qx, self.qy):
[fc4fec8]492                raise ValueError("Invalid Q input")
[01cda57]493        except:
494            msg = "An error occurred during the resolution computation."
495            msg += "Please check your inputs..."
496            logging.warning(msg)
497            return
498
499        # Validate the q inputs
500        q_input = self._validate_q_input(self.qx, self.qy)
501        if q_input is not None:
502            self.qx, self.qy = q_input
503
504        # Make list of q min max for mapping
505        for i in range(len(self.qx)):
506            qx_min.append(xmin)
507            qx_max.append(xmax)
508        for i in range(len(self.qy)):
509            qy_min.append(ymin)
510            qy_max.append(ymax)
511
512        # Compute the resolution
513        if self.image is not None:
514            self.resolution.reset_image()
515
516        # Compute and get the image plot
517        try:
[fc4fec8]518            cal_res = threads.deferToThread(self.map_wrapper,
519                                            self.calc_func,
520                                            self.qx,
521                                            self.qy,
522                                            qx_min,
523                                            qx_max,
524                                            qy_min, qy_max)
525
526            cal_res.addCallback(self.complete)
[7fb471d]527            cal_res.addErrback(self.calculateFailed)
[fc4fec8]528
529            # logging.info("Computation is in progress...")
[01cda57]530            self.cmdCompute.setText('Wait...')
531            self.cmdCompute.setEnabled(False)
532        except:
533            raise
534
[7fb471d]535    def calculateFailed(self, reason):
536        print("calculateFailed Failed with:\n", reason)
537        pass
538
[fc4fec8]539    def complete(self, image):
[01cda57]540        """
541        Complete computation
542        """
543        self.image = image
544
545        # Get and format the sigmas
546        sigma_r = self.formatNumber(self.resolution.sigma_1)
547        sigma_phi = self.formatNumber(self.resolution.sigma_2)
548        sigma_lamd = self.formatNumber(self.resolution.sigma_lamd)
549        sigma_1d = self.formatNumber(self.resolution.sigma_1d)
550
551        # Set output values
552        self.txtSigma_x.setText(str(sigma_r))
553        self.txtSigma_y.setText(str(sigma_phi))
554        self.txtSigma_lamd.setText(str(sigma_lamd))
555        self.txt1DSigma.setText(str(sigma_1d))
556
557        self.cmdCompute.setText('Compute')
558        self.cmdCompute.setEnabled(True)
[fc4fec8]559
560        self.new2DPlot()
561
[01cda57]562        return
563
[fc4fec8]564    def map_wrapper(self, func, qx, qy, qx_min, qx_max, qy_min, qy_max):
[01cda57]565        """
566        Prepare the Mapping for the computation
567        : params qx, qy, qx_min, qx_max, qy_min, qy_max:
568        : return: image (numpy array)
569        """
[7fb471d]570        # This fails in py3 with
571        # [Failure instance: Traceback: <class 'TypeError'>: 'map' object is not subscriptable
572        # INVESTIGATE
[fc4fec8]573        image = map(func, qx, qy,
574                    qx_min, qx_max,
575                    qy_min, qy_max)[0]
576        return image
577
578    def calc_func(self, qx, qy, qx_min, qx_max, qy_min, qy_max):
579        """
580        Perform the calculation for a given set of Q values.
581        : return: image (numpy array)
582        """
[01cda57]583        try:
584            qx_value = float(qx)
585            qy_value = float(qy)
[fc4fec8]586        except :
587            raise ValueError
588
[01cda57]589        # calculate 2D resolution distribution image
590        image = self.resolution.compute_and_plot(qx_value, qy_value,
591                                                 qx_min, qx_max, qy_min,
592                                                 qy_max,
593                                                 self.det_coordinate)
594        return image
595
596    # #################################
597    # Legacy validators
598    # #################################
[fc4fec8]599    def _string2list(self, input_string):
[01cda57]600        """
601        Change NNN, NNN to list,ie. [NNN, NNN] where NNN is a number
602        """
[fc4fec8]603        new_numbers_list = []
[01cda57]604        # check the number of floats
605        try:
[fc4fec8]606            strg = float(input_string)
607            new_numbers_list.append(strg)
[01cda57]608        except:
[fc4fec8]609            string_split = input_string.split(',')
[170e95d]610            if len(string_split) == 1 or len(string_split) == 2:
611                new_numbers_list = [float(item) for item in string_split]
[01cda57]612            else:
[fc4fec8]613                msg = "The numbers must be one or two (separated by ',')"
[01cda57]614                logging.info(msg)
[b3e8629]615                raise RuntimeError(msg)
[01cda57]616
[fc4fec8]617        return new_numbers_list
[01cda57]618
[fc4fec8]619    def _string2inputlist(self, input_string):
[01cda57]620        """
621        Change NNN, NNN,... to list,ie. [NNN, NNN,...] where NNN is a number
[fc4fec8]622        : return new_list: string like list
[01cda57]623        """
[fc4fec8]624        new_list = []
625        string_split = input_string.split(',')
626        try:
627            new_list = [float(t) for t in string_split]
628        except:
[b3e8629]629            logging.error(sys.exc_info()[1])
[fc4fec8]630        return new_list
[01cda57]631
[fc4fec8]632    def _str2longlist(self, input_string):
[01cda57]633        """
[fc4fec8]634          Change NNN, NNN,... to list, NNN - NNN ; NNN to list, or float to list
635          : return new_string: string like list
636          """
[01cda57]637        try:
638            # is float
[fc4fec8]639            out = [float(input_string)]
[01cda57]640            return out
641        except:
642            if self.cbWaveColor.currentText() == 'Monochromatic':
643                logging.warning("Wrong format of inputs.")
644            else:
645                try:
646                    # has a '-'
[fc4fec8]647                    if input_string.count('-') > 0:
648                        value = input_string.split('-')
[01cda57]649                        if value[1].count(';') > 0:
650                            # has a ';'
651                            last_list = value[1].split(';')
[fc4fec8]652                            num = numpy.ceil(float(last_list[1]))
[01cda57]653                            max_value = float(last_list[0])
654                            self.num_wave = num
655                        else:
656                            # default num
657                            num = self.num_wave
658                            max_value = float(value[1])
659                        min_value = float(value[0])
660                        # make a list
[fc4fec8]661                        bin_size = numpy.fabs(max_value - min_value) / (num - 1)
[01cda57]662                        out = [min_value + bin_size * bnum for bnum in
663                               range(num)]
664                        return out
[fc4fec8]665                    if input_string.count(',') > 0:
666                        out = self._string2inputlist(input_string)
[01cda57]667                        return out
668                except:
[b3e8629]669                    logging.error(sys.exc_info()[1])
[01cda57]670
671    def _validate_q_input(self, qx, qy):
672        """
673        Check if q inputs are valid
674        : params qx:  qx as a list
675        : params qy:  qy as a list
676        : return: True/False
677        """
678        # check qualifications
679        if qx.__class__.__name__ != 'list':
680            return None
681        if qy.__class__.__name__ != 'list':
682            return None
683        if len(qx) < 1:
684            return None
685        if len(qy) < 1:
686            return None
687        # allow one input
688        if len(qx) == 1 and len(qy) > 1:
689            qx = [qx[0] for ind in range(len(qy))]
690
691        if len(qy) == 1 and len(qx) > 1:
692            qy = [qy[0] for ind in range(len(qx))]
693        # check length
694        if len(qx) != len(qy):
695            return None
696        if qx is None or qy is None:
697            return None
698        return qx, qy
699
700    def formatNumber(self, value=None):
701        """
702        Return a float in a standardized, human-readable formatted string
703        """
704        try:
705            value = float(value)
706        except:
707            output = None
708            return output
709
710        output = "%-7.4g" % value
711        return output.lstrip().rstrip()
712
713    # #################################
714    # Plot
715    # #################################
716
717    def createTemplate2DPlot(self):
718        """
719        Create a template for 2D data
720        """
721        self.plotter = Plotter2DWidget(self, quickplot=True)
722        self.plotter.scale = 'linear'
[fc4fec8]723        self.plotter.cmap = None
[4992ff2]724        layout = QtWidgets.QHBoxLayout()
[01cda57]725        layout.setContentsMargins(0, 0, 0, 0)
726        self.graphicsView.setLayout(layout)
727        layout.addWidget(self.plotter)
728
[fc4fec8]729    def new2DPlot(self):
[01cda57]730        """
731        Create a new 2D data instance based on computing results
732        """
733        qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
734
735        dx_size = (qx_max - qx_min) / (1000 - 1)
736        dy_size = (qy_max - qy_min) / (1000 - 1)
737        x_val = numpy.arange(qx_min, qx_max, dx_size)
738        y_val = numpy.arange(qy_max, qy_min, -dy_size)
739
[170e95d]740        if len(self.plotter.ax.patches):
741            self.plotter.ax.patches[0].remove()
742
[01cda57]743        self.drawLines()
744
745        self.plotter.data = Data2D(image=self.image,
746                      qx_data=x_val,
747                      qy_data=y_val,
748                      xmin=qx_min, xmax=qx_max,
749                      ymin=qy_min, ymax=qy_max)
750
751        self.plotter.plot()
752        self.plotter.show()
753
754    def drawLines(self):
755        """
756        Draw lines in image if applicable
757        """
758        wave_list, _ = self.resolution.get_wave_list()
759        if len(wave_list) > 1 and wave_list[-1] == max(wave_list):
760            color = 'g'
761            # draw a green rectangle(limit for the longest wavelength
762            # to be involved) for tof inputs
763            # Get the params from resolution
764            # plotting range for largest wavelength
765            qx_min = self.resolution.qx_min
766            qx_max = self.resolution.qx_max
767            qy_min = self.resolution.qy_min
768            qy_max = self.resolution.qy_max
769            # detector range
770            detector_qx_min = self.resolution.detector_qx_min
771            detector_qx_max = self.resolution.detector_qx_max
772            detector_qy_min = self.resolution.detector_qy_min
773            detector_qy_max = self.resolution.detector_qy_max
774
775            rect = patches.Rectangle((detector_qx_min + 0.0002,
776                                      detector_qy_min + 0.0002),
777                                     detector_qx_max - detector_qx_min,
778                                     detector_qy_max - detector_qy_min,
779                                     linewidth=2,
780                                     edgecolor=color, facecolor='none')
781            self.plotter.ax.add_patch(rect)
782        else:
783            qx_min, qx_max, qy_min, qy_max = self.resolution.get_detector_qrange()
784            # detector range
785            detector_qx_min = self.resolution.qxmin_limit
786            detector_qx_max = self.resolution.qxmax_limit
787            detector_qy_min = self.resolution.qymin_limit
788            detector_qy_max = self.resolution.qymax_limit
789
790            xmin = min(self.qx)
791            xmax = max(self.qx)
792            ymin = min(self.qy)
793            ymax = max(self.qy)
794
795            if xmin < detector_qx_min or xmax > detector_qx_max or \
796                            ymin < detector_qy_min or ymax > detector_qy_max:
797                # message
798                msg = 'At least one q value located out side of\n'
799                msg += " the detector range (%s < qx < %s, %s < qy < %s),\n" % \
800                       (self.formatNumber(detector_qx_min),
801                        self.formatNumber(detector_qx_max),
802                        self.formatNumber(detector_qy_min),
803                        self.formatNumber(detector_qy_max))
804                msg += " is ignored in computation.\n"
805
806                logging.warning(msg)
807
808        # Draw zero axis lines.
809        if qy_min < 0 <= qy_max:
810            self.plotter.ax.axhline(linewidth=1)
811
812        if qx_min < 0 <= qx_max:
813            self.plotter.ax.axvline(linewidth=1)
Note: See TracBrowser for help on using the repository browser.