source: sasview/src/sas/qtgui/Calculators/GenericScatteringCalculator.py @ 457d961

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 457d961 was 457d961, checked in by Celine Durniak <celine.durniak@…>, 7 years ago

Corrected bugs in display of new GUI (angstrom, size of line edit)

  • Property mode set to 100755
File size: 33.3 KB
Line 
1import sys
2import os
3import numpy
4import logging
5import time
6
7from PyQt4 import QtGui
8from PyQt4 import QtCore
9from twisted.internet import threads
10from mpl_toolkits.mplot3d import Axes3D
11
12import sas.qtgui.Utilities.GuiUtils as GuiUtils
13
14from sas.qtgui.Utilities.GenericReader import GenReader
15
16from sas.sascalc.dataloader.data_info import Detector
17from sas.sascalc.dataloader.data_info import Source
18from sas.sascalc.calculator import sas_gen
19
20from sas.qtgui.Plotting.Arrow3D import Arrow3D
21from sas.qtgui.Plotting.PlotterBase import PlotterBase
22from sas.qtgui.Plotting.Plotter2D import Plotter2D
23from sas.qtgui.Plotting.Plotter import Plotter
24from sas.qtgui.Plotting.PlotterData import Data1D
25from sas.qtgui.Plotting.PlotterData import Data2D
26
27# Local UI
28from UI.GenericScatteringCalculator import Ui_GenericScatteringCalculator
29
30_Q1D_MIN = 0.001
31
32
33class GenericScatteringCalculator(QtGui.QDialog, Ui_GenericScatteringCalculator):
34
35    trigger_plot_3d = QtCore.pyqtSignal()
36
37    def __init__(self, parent=None):
38        super(GenericScatteringCalculator, self).__init__()
39        self.setupUi(self)
40        self.manager = parent
41        self.communicator = self.manager.communicator()
42        self.model = sas_gen.GenSAS()
43        self.omf_reader = sas_gen.OMFReader()
44        self.sld_reader = sas_gen.SLDReader()
45        self.pdb_reader = sas_gen.PDBReader()
46        self.reader = None
47        self.sld_data = None
48
49        self.parameters = []
50        self.data = None
51        self.datafile = None
52        self.file_name = ''
53        self.ext = None
54        self.default_shape = str(self.cbShape.currentText())
55        self.is_avg = False
56        self.data_to_plot = None
57
58        # combox box
59        self.cbOptionsCalc.setVisible(False)
60
61        # push buttons
62        self.cmdClose.clicked.connect(self.accept)
63        self.cmdHelp.clicked.connect(self.onHelp)
64
65        self.cmdLoad.clicked.connect(self.loadFile)
66        self.cmdCompute.clicked.connect(self.onCompute)
67        self.cmdReset.clicked.connect(self.onReset)
68        self.cmdSave.clicked.connect(self.onSaveFile)
69
70        self.cmdDraw.clicked.connect(lambda: self.plot3d(has_arrow=True))
71        self.cmdDrawpoints.clicked.connect(lambda: self.plot3d(has_arrow=False))
72
73        # validators
74        # scale, volume and background must be positive
75        validat_regex_pos = QtCore.QRegExp('^[+]?([.]\d+|\d+([.]\d+)?)$')
76        self.txtScale.setValidator(QtGui.QRegExpValidator(validat_regex_pos,
77                                                          self.txtScale))
78        self.txtBackground.setValidator(QtGui.QRegExpValidator(
79            validat_regex_pos, self.txtBackground))
80        self.txtTotalVolume.setValidator(QtGui.QRegExpValidator(
81            validat_regex_pos, self.txtTotalVolume))
82
83        # fraction of spin up between 0 and 1
84        validat_regexbetween0_1 = QtCore.QRegExp('^(0(\.\d*)*|1(\.0+)?)$')
85        self.txtUpFracIn.setValidator(
86            QtGui.QRegExpValidator(validat_regexbetween0_1, self.txtUpFracIn))
87        self.txtUpFracOut.setValidator(
88            QtGui.QRegExpValidator(validat_regexbetween0_1, self.txtUpFracOut))
89
90        # 0 < Qmax <= 1000
91        validat_regex_q = QtCore.QRegExp('^1000$|^[+]?(\d{1,3}([.]\d+)?)$')
92        self.txtQxMax.setValidator(QtGui.QRegExpValidator(validat_regex_q,
93                                                          self.txtQxMax))
94        self.txtQxMax.textChanged.connect(self.check_value)
95
96        # 2 <= Qbin <= 1000
97        self.txtNoQBins.setValidator(QtGui.QRegExpValidator(validat_regex_q,
98                                                            self.txtNoQBins))
99        self.txtNoQBins.textChanged.connect(self.check_value)
100
101        # plots - 3D in real space
102        self.trigger_plot_3d.connect(lambda: self.plot3d(has_arrow=False))
103
104        self.graph_num = 1  # index for name of graph
105
106        # TODO the option Ellipsoid has not been implemented
107        self.cbShape.currentIndexChanged.connect(self.selectedshapechange)
108
109        # New font to display angstrom symbol
110        new_font = 'font-family: -apple-system, "Helvetica Neue", "Ubuntu";'
111        self.lblUnitSolventSLD.setStyleSheet(new_font)
112        self.lblUnitVolume.setStyleSheet(new_font)
113        self.lbl5.setStyleSheet(new_font)
114        self.lblUnitMx.setStyleSheet(new_font)
115        self.lblUnitMy.setStyleSheet(new_font)
116        self.lblUnitMz.setStyleSheet(new_font)
117        self.lblUnitNucl.setStyleSheet(new_font)
118        self.lblUnitx.setStyleSheet(new_font)
119        self.lblUnity.setStyleSheet(new_font)
120        self.lblUnitz.setStyleSheet(new_font)
121
122    def selectedshapechange(self):
123        """
124        TODO Temporary solution to display information about option 'Ellipsoid'
125        """
126        print "The option Ellipsoid has not been implemented yet."
127        self.communicator.statusBarUpdateSignal.emit(
128            "The option Ellipsoid has not been implemented yet.")
129
130    def loadFile(self):
131        """
132        Open menu to choose the datafile to load
133        Only extensions .SLD, .PDB, .OMF, .sld, .pdb, .omf
134        """
135        try:
136            self.datafile = QtGui.QFileDialog.getOpenFileName(
137                self, "Choose a file", "", "All Gen files (*.OMF *.omf) ;;"
138                                          "SLD files (*.SLD *.sld);;PDB files (*.pdb *.PDB);; "
139                                          "OMF files (*.OMF *.omf);; "
140                                          "All files (*.*)")
141            if self.datafile:
142                self.default_shape = str(self.cbShape.currentText())
143                self.file_name = os.path.basename(str(self.datafile))
144                self.ext = os.path.splitext(str(self.datafile))[1]
145                if self.ext in self.omf_reader.ext:
146                    loader = self.omf_reader
147                elif self.ext in self.sld_reader.ext:
148                    loader = self.sld_reader
149                elif self.ext in self.pdb_reader.ext:
150                    loader = self.pdb_reader
151                else:
152                    loader = None
153                # disable some entries depending on type of loaded file
154                # (according to documentation)
155                if self.ext.lower() in ['.sld', '.omf', '.pdb']:
156                    self.txtUpFracIn.setEnabled(False)
157                    self.txtUpFracOut.setEnabled(False)
158                    self.txtUpTheta.setEnabled(False)
159
160                if self.reader is not None and self.reader.isrunning():
161                    self.reader.stop()
162                self.cmdLoad.setEnabled(False)
163                self.cmdLoad.setText('Loading...')
164                self.communicator.statusBarUpdateSignal.emit(
165                    "Loading File {}".format(os.path.basename(
166                        str(self.datafile))))
167                self.reader = GenReader(path=str(self.datafile), loader=loader,
168                                        completefn=self.complete_loading,
169                                        updatefn=self.load_update)
170                self.reader.queue()
171        except (RuntimeError, IOError):
172            log_msg = "Generic SAS Calculator: %s" % sys.exc_value
173            logging.info(log_msg)
174            raise
175        return
176
177    def load_update(self):
178        """ Legacy function used in GenRead """
179        if self.reader.isrunning():
180            status_type = "progress"
181        else:
182            status_type = "stop"
183        logging.info(status_type)
184
185    def complete_loading(self, data=None):
186        """ Function used in GenRead"""
187        self.cbShape.setEnabled(False)
188        try:
189            is_pdbdata = False
190            self.txtData.setText(os.path.basename(str(self.datafile)))
191            self.is_avg = False
192            if self.ext in self.omf_reader.ext:
193                gen = sas_gen.OMF2SLD()
194                gen.set_data(data)
195                self.sld_data = gen.get_magsld()
196                self.check_units()
197            elif self.ext in self.sld_reader.ext:
198                self.sld_data = data
199            elif self.ext in self.pdb_reader.ext:
200                self.sld_data = data
201                is_pdbdata = True
202            # Display combobox of orientation only for pdb data
203            self.cbOptionsCalc.setVisible(is_pdbdata)
204            self.update_gui()
205        except IOError:
206            log_msg = "Loading Error: " \
207                      "This file format is not supported for GenSAS."
208            logging.info(log_msg)
209            raise
210        except ValueError:
211            log_msg = "Could not find any data"
212            logging.info(log_msg)
213            raise
214        logging.info("Load Complete")
215        self.cmdLoad.setEnabled(True)
216        self.cmdLoad.setText('Load')
217        self.trigger_plot_3d.emit()
218
219    def check_units(self):
220        """
221        Check if the units from the OMF file correspond to the default ones
222        displayed on the interface.
223        If not, modify the GUI with the correct unit
224        """
225        #  TODO: adopt the convention of font and symbol for the updated values
226        if sas_gen.OMFData().valueunit != 'A^(-2)':
227            value_unit = sas_gen.OMFData().valueunit
228            self.lbl_unitMx.setText(value_unit)
229            self.lbl_unitMy.setText(value_unit)
230            self.lbl_unitMz.setText(value_unit)
231            self.lbl_unitNucl.setText(value_unit)
232        if sas_gen.OMFData().meshunit != 'A':
233            mesh_unit = sas_gen.OMFData().meshunit
234            self.lbl_unitx.setText(mesh_unit)
235            self.lbl_unity.setText(mesh_unit)
236            self.lbl_unitz.setText(mesh_unit)
237            self.lbl_unitVolume.setText(mesh_unit+"^3")
238
239    def check_value(self):
240        """Check range of text edits for QMax and Number of Qbins """
241        text_edit = self.sender()
242        text_edit.setStyleSheet(
243            QtCore.QString.fromUtf8('background-color: rgb(255, 255, 255);'))
244        if text_edit.text():
245            value = float(str(text_edit.text()))
246            if text_edit == self.txtQxMax:
247                if value <= 0 or value > 1000:
248                    text_edit.setStyleSheet(QtCore.QString.fromUtf8(
249                        'background-color: rgb(255, 182, 193);'))
250                else:
251                    text_edit.setStyleSheet(QtCore.QString.fromUtf8(
252                        'background-color: rgb(255, 255, 255);'))
253            elif text_edit == self.txtNoQBins:
254                if value < 2 or value > 1000:
255                    self.txtNoQBins.setStyleSheet(QtCore.QString.fromUtf8(
256                        'background-color: rgb(255, 182, 193);'))
257                else:
258                    self.txtNoQBins.setStyleSheet(QtCore.QString.fromUtf8(
259                        'background-color: rgb(255, 255, 255);'))
260
261    def update_gui(self):
262        """ Update the interface with values from loaded data """
263        self.model.set_is_avg(self.is_avg)
264        self.model.set_sld_data(self.sld_data)
265        self.model.params['total_volume'] = len(self.sld_data.sld_n)*self.sld_data.vol_pix[0]
266
267        # add condition for activation of save button
268        self.cmdSave.setEnabled(True)
269
270        # activation of 3D plots' buttons (with and without arrows)
271        self.cmdDraw.setEnabled(self.sld_data is not None)
272        self.cmdDrawpoints.setEnabled(self.sld_data is not None)
273
274        self.txtScale.setText(str(self.model.params['scale']))
275        self.txtBackground.setText(str(self.model.params['background']))
276        self.txtSolventSLD.setText(str(self.model.params['solvent_SLD']))
277
278        # Volume to write to interface: npts x volume of first pixel
279        self.txtTotalVolume.setText(str(len(self.sld_data.sld_n)*self.sld_data.vol_pix[0]))
280
281        self.txtUpFracIn.setText(str(self.model.params['Up_frac_in']))
282        self.txtUpFracOut.setText(str(self.model.params['Up_frac_out']))
283        self.txtUpTheta.setText(str(self.model.params['Up_theta']))
284
285        self.txtNoPixels.setText(str(len(self.sld_data.sld_n)))
286        self.txtNoPixels.setEnabled(False)
287
288        list_parameters = ['sld_mx', 'sld_my', 'sld_mz', 'sld_n', 'xnodes',
289                           'ynodes', 'znodes', 'xstepsize', 'ystepsize',
290                           'zstepsize']
291        list_gui_button = [self.txtMx, self.txtMy, self.txtMz, self.txtNucl,
292                           self.txtXnodes, self.txtYnodes, self.txtZnodes,
293                           self.txtXstepsize, self.txtYstepsize,
294                           self.txtZstepsize]
295
296        # Fill right hand side of GUI
297        for indx, item in enumerate(list_parameters):
298            if getattr(self.sld_data, item) is None:
299                list_gui_button[indx].setText('NaN')
300            else:
301                value = getattr(self.sld_data, item)
302                if isinstance(value, numpy.ndarray):
303                    item_for_gui = str(GuiUtils.formatNumber(numpy.average(value), True))
304                else:
305                    item_for_gui = str(GuiUtils.formatNumber(value, True))
306                list_gui_button[indx].setText(item_for_gui)
307
308        # Enable / disable editing of right hand side of GUI
309        for indx, item in enumerate(list_parameters):
310            if indx < 4:
311                # this condition only applies to Mx,y,z and Nucl
312                value = getattr(self.sld_data, item)
313                enable = self.sld_data.pix_type == 'pixel' \
314                         and numpy.min(value) == numpy.max(value)
315            else:
316                enable = not self.sld_data.is_data
317            list_gui_button[indx].setEnabled(enable)
318
319    def write_new_values_from_gui(self):
320        """
321        update parameters using modified inputs from GUI
322        used before computing
323        """
324        if self.txtScale.isModified():
325            self.model.params['scale'] = float(self.txtScale.text())
326
327        if self.txtBackground.isModified():
328            self.model.params['background'] = float(self.txtBackground.text())
329
330        if self.txtSolventSLD.isModified():
331            self.model.params['solvent_SLD'] = float(self.txtSolventSLD.text())
332
333        # Different condition for total volume to get correct volume after
334        # applying set_sld_data in compute
335        if self.txtTotalVolume.isModified() \
336                or self.model.params['total_volume'] != float(self.txtTotalVolume.text()):
337            self.model.params['total_volume'] = float(self.txtTotalVolume.text())
338
339        if self.txtUpFracIn.isModified():
340            self.model.params['Up_frac_in'] = float(self.txtUpFracIn.text())
341
342        if self.txtUpFracOut.isModified():
343            self.model.params['Up_frac_out'] = float(self.txtUpFracOut.text())
344
345        if self.txtUpTheta.isModified():
346            self.model.params['Up_theta'] = float(self.txtUpTheta.text())
347
348        if self.txtMx.isModified():
349            self.sld_data.sld_mx = float(self.txtMx.text())*\
350                                   numpy.ones(len(self.sld_data.sld_mx))
351
352        if self.txtMy.isModified():
353            self.sld_data.sld_my = float(self.txtMy.text())*\
354                                   numpy.ones(len(self.sld_data.sld_my))
355
356        if self.txtMz.isModified():
357            self.sld_data.sld_mz = float(self.txtMz.text())*\
358                                   numpy.ones(len(self.sld_data.sld_mz))
359
360        if self.txtNucl.isModified():
361            self.sld_data.sld_n = float(self.txtNucl.text())*\
362                                  numpy.ones(len(self.sld_data.sld_n))
363
364        if self.txtXnodes.isModified():
365            self.sld_data.xnodes = int(self.txtXnodes.text())
366
367        if self.txtYnodes.isModified():
368            self.sld_data.ynodes = int(self.txtYnodes.text())
369
370        if self.txtZnodes.isModified():
371            self.sld_data.znodes = int(self.txtZnodes.text())
372
373        if self.txtXstepsize.isModified():
374            self.sld_data.xstepsize = float(self.txtXstepsize.text())
375
376        if self.txtYstepsize.isModified():
377            self.sld_data.ystepsize = float(self.txtYstepsize.text())
378
379        if self.txtZstepsize.isModified():
380            self.sld_data.zstepsize = float(self.txtZstepsize.text())
381
382        if self.cbOptionsCalc.isVisible():
383            self.is_avg = (self.cbOptionsCalc.currentIndex() == 1)
384
385    def onHelp(self):
386        """
387        Bring up the Generic Scattering calculator Documentation whenever
388        the HELP button is clicked.
389        Calls Documentation Window with the path of the location within the
390        documentation tree (after /doc/ ....".
391        """
392        try:
393            location = GuiUtils.HELP_DIRECTORY_LOCATION + \
394                       "/user/sasgui/perspectives/calculator/sas_calculator_help.html"
395            self.manager._helpView.load(QtCore.QUrl(location))
396            self.manager._helpView.show()
397        except AttributeError:
398            # No manager defined - testing and standalone runs
399            pass
400
401    def onReset(self):
402        """ Reset the inputs of textEdit to default values """
403        try:
404            # reset values in textedits
405            self.txtUpFracIn.setText("1.0")
406            self.txtUpFracOut.setText("1.0")
407            self.txtUpTheta.setText("0.0")
408            self.txtBackground.setText("0.0")
409            self.txtScale.setText("1.0")
410            self.txtSolventSLD.setText("0.0")
411            self.txtTotalVolume.setText("216000.0")
412            self.txtNoQBins.setText("50")
413            self.txtQxMax.setText("0.3")
414            self.txtNoPixels.setText("1000")
415            self.txtMx.setText("0")
416            self.txtMy.setText("0")
417            self.txtMz.setText("0")
418            self.txtNucl.setText("6.97e-06")
419            self.txtXnodes.setText("10")
420            self.txtYnodes.setText("10")
421            self.txtZnodes.setText("10")
422            self.txtXstepsize.setText("6")
423            self.txtYstepsize.setText("6")
424            self.txtZstepsize.setText("6")
425            # reset Load button and textedit
426            self.txtData.setText('Default SLD Profile')
427            self.cmdLoad.setEnabled(True)
428            self.cmdLoad.setText('Load')
429            # reset option for calculation
430            self.cbOptionsCalc.setCurrentIndex(0)
431            self.cbOptionsCalc.setVisible(False)
432            # reset shape button
433            self.cbShape.setCurrentIndex(0)
434            self.cbShape.setEnabled(True)
435            # reset compute button
436            self.cmdCompute.setText('Compute')
437            self.cmdCompute.setEnabled(True)
438            # TODO reload default data set
439            self._create_default_sld_data()
440
441        finally:
442            pass
443
444    def _create_default_2d_data(self):
445        """
446        Copied from previous version
447        Create 2D data by default
448        :warning: This data is never plotted.
449        """
450        self.qmax_x = float(self.txtQxMax.text())
451        self.npts_x = int(self.txtNoQBins.text())
452        self.data = Data2D()
453        self.data.is_data = False
454        # # Default values
455        self.data.detector.append(Detector())
456        index = len(self.data.detector) - 1
457        self.data.detector[index].distance = 8000  # mm
458        self.data.source.wavelength = 6  # A
459        self.data.detector[index].pixel_size.x = 5  # mm
460        self.data.detector[index].pixel_size.y = 5  # mm
461        self.data.detector[index].beam_center.x = self.qmax_x
462        self.data.detector[index].beam_center.y = self.qmax_x
463        xmax = self.qmax_x
464        xmin = -xmax
465        ymax = self.qmax_x
466        ymin = -ymax
467        qstep = self.npts_x
468
469        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
470        y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
471        # use data info instead
472        new_x = numpy.tile(x, (len(y), 1))
473        new_y = numpy.tile(y, (len(x), 1))
474        new_y = new_y.swapaxes(0, 1)
475        # all data require now in 1d array
476        qx_data = new_x.flatten()
477        qy_data = new_y.flatten()
478        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
479        # set all True (standing for unmasked) as default
480        mask = numpy.ones(len(qx_data), dtype=bool)
481        self.data.source = Source()
482        self.data.data = numpy.ones(len(mask))
483        self.data.err_data = numpy.ones(len(mask))
484        self.data.qx_data = qx_data
485        self.data.qy_data = qy_data
486        self.data.q_data = q_data
487        self.data.mask = mask
488        # store x and y bin centers in q space
489        self.data.x_bins = x
490        self.data.y_bins = y
491        # max and min taking account of the bin sizes
492        self.data.xmin = xmin
493        self.data.xmax = xmax
494        self.data.ymin = ymin
495        self.data.ymax = ymax
496
497    def _create_default_sld_data(self):
498        """
499        Copied from previous version
500        Making default sld-data
501        """
502        sld_n_default = 6.97e-06  # what is this number??
503        omfdata = sas_gen.OMFData()
504        omf2sld = sas_gen.OMF2SLD()
505        omf2sld.set_data(omfdata, self.default_shape)
506        self.sld_data = omf2sld.output
507        self.sld_data.is_data = False
508        self.sld_data.filename = "Default SLD Profile"
509        self.sld_data.set_sldn(sld_n_default)
510
511    def _create_default_1d_data(self):
512        """
513        Copied from previous version
514        Create 1D data by default
515        :warning: This data is never plotted.
516                    residuals.x = data_copy.x[index]
517            residuals.dy = numpy.ones(len(residuals.y))
518            residuals.dx = None
519            residuals.dxl = None
520            residuals.dxw = None
521        """
522        self.qmax_x = float(self.txtQxMax.text())
523        self.npts_x = int(self.txtNoQBins.text())
524        #  Default values
525        xmax = self.qmax_x
526        xmin = self.qmax_x * _Q1D_MIN
527        qstep = self.npts_x
528        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
529        # store x and y bin centers in q space
530        y = numpy.ones(len(x))
531        dy = numpy.zeros(len(x))
532        dx = numpy.zeros(len(x))
533        self.data = Data1D(x=x, y=y)
534        self.data.dx = dx
535        self.data.dy = dy
536
537    def onCompute(self):
538        """
539        Copied from previous version
540        Execute the computation of I(qx, qy)
541        """
542        # Set default data when nothing loaded yet
543        if self.sld_data is None:
544            self._create_default_sld_data()
545        try:
546            self.model.set_sld_data(self.sld_data)
547            self.write_new_values_from_gui()
548            if self.is_avg or self.is_avg is None:
549                self._create_default_1d_data()
550                i_out = numpy.zeros(len(self.data.y))
551                inputs = [self.data.x, [], i_out]
552            else:
553                self._create_default_2d_data()
554                i_out = numpy.zeros(len(self.data.data))
555                inputs = [self.data.qx_data, self.data.qy_data, i_out]
556            logging.info("Computation is in progress...")
557            self.cmdCompute.setText('Wait...')
558            self.cmdCompute.setEnabled(False)
559            d = threads.deferToThread(self.complete, inputs, self._update)
560            # Add deferred callback for call return
561            d.addCallback(self.plot_1_2d)
562        except:
563            log_msg = "{}. stop".format(sys.exc_value)
564            logging.info(log_msg)
565        return
566
567    def _update(self, value):
568        """
569        Copied from previous version
570        """
571        pass
572
573    def complete(self, input, update=None):
574        """
575        Gen compute complete function
576        :Param input: input list [qx_data, qy_data, i_out]
577        """
578        out = numpy.empty(0)
579        for ind in range(len(input[0])):
580            if self.is_avg:
581                if ind % 1 == 0 and update is not None:
582                    # update()
583                    percentage = int(100.0 * float(ind) / len(input[0]))
584                    update(percentage)
585                    time.sleep(0.001)  # 0.1
586                inputi = [input[0][ind:ind + 1], [], input[2][ind:ind + 1]]
587                outi = self.model.run(inputi)
588                out = numpy.append(out, outi)
589            else:
590                if ind % 50 == 0 and update is not None:
591                    percentage = int(100.0 * float(ind) / len(input[0]))
592                    update(percentage)
593                    time.sleep(0.001)
594                inputi = [input[0][ind:ind + 1], input[1][ind:ind + 1],
595                          input[2][ind:ind + 1]]
596                outi = self.model.runXY(inputi)
597                out = numpy.append(out, outi)
598        self.data_to_plot = out
599        logging.info('Gen computation completed.')
600        self.cmdCompute.setText('Compute')
601        self.cmdCompute.setEnabled(True)
602        return
603
604    def onSaveFile(self):
605        """Save data as .sld file"""
606        path = os.path.dirname(str(self.datafile))
607        default_name = os.path.join(path, 'sld_file')
608        kwargs = {
609            'parent': self,
610            'directory': default_name,
611            'filter': 'SLD file (*.sld)',
612            'options': QtGui.QFileDialog.DontUseNativeDialog}
613        # Query user for filename.
614        filename = str(QtGui.QFileDialog.getSaveFileName(**kwargs))
615        if filename:
616            try:
617                if os.path.splitext(filename)[1].lower() == '.sld':
618                    sas_gen.SLDReader().write(filename, self.sld_data)
619                else:
620                    sas_gen.SLDReader().write('.'.join((filename, 'sld')),
621                                              self.sld_data)
622            except:
623                raise
624
625    def plot3d(self, has_arrow=False):
626        """ Generate 3D plot in real space with or without arrows """
627        self.write_new_values_from_gui()
628        graph_title = " Graph {}: {} 3D SLD Profile".format(self.graph_num,
629                                                            self.file_name)
630        if has_arrow:
631            graph_title += ' - Magnetic Vector as Arrow'
632
633        plot3D = Plotter3D(self, graph_title)
634        plot3D.plot(self.sld_data, has_arrow=has_arrow)
635        plot3D.show()
636        self.graph_num += 1
637
638    def plot_1_2d(self, d):
639        """ Generate 1D or 2D plot, called in Compute"""
640        if self.is_avg or self.is_avg is None:
641            data = Data1D(x=self.data.x, y=self.data_to_plot)
642            data.title = "GenSAS {}  #{} 1D".format(self.file_name,
643                                                    int(self.graph_num))
644            data.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
645            data.yaxis('\\rm{Intensity}', 'cm^{-1}')
646            plot1D = Plotter(self)
647            plot1D.plot(data)
648            plot1D.show()
649            self.graph_num += 1
650            # TODO
651            print 'TRANSFER OF DATA TO MAIN PANEL TO BE IMPLEMENTED'
652            return plot1D
653        else:
654            numpy.nan_to_num(self.data_to_plot)
655            data = Data2D(image=self.data_to_plot,
656                          qx_data=self.data.qx_data,
657                          qy_data=self.data.qy_data,
658                          q_data=self.data.q_data,
659                          xmin=self.data.xmin, xmax=self.data.ymax,
660                          ymin=self.data.ymin, ymax=self.data.ymax,
661                          err_image=self.data.err_data)
662            data.title = "GenSAS {}  #{} 2D".format(self.file_name,
663                                                    int(self.graph_num))
664            plot2D = Plotter2D(self)
665            plot2D.plot(data)
666            plot2D.show()
667            self.graph_num += 1
668            # TODO
669            print 'TRANSFER OF DATA TO MAIN PANEL TO BE IMPLEMENTED'
670            return plot2D
671
672
673class Plotter3DWidget(PlotterBase):
674    """
675    3D Plot widget for use with a QDialog
676    """
677    def __init__(self, parent=None, manager=None):
678        super(Plotter3DWidget, self).__init__(parent,  manager=manager)
679
680    @property
681    def data(self):
682        return self._data
683
684    @data.setter
685    def data(self, data=None):
686        """ data setter """
687        self._data = data
688
689    def plot(self, data=None, has_arrow=False):
690        """
691        Plot 3D self._data
692        """
693        self.data = data
694        assert(self._data)
695        # Prepare and show the plot
696        self.showPlot(data=self.data, has_arrow=has_arrow)
697
698    def showPlot(self, data, has_arrow=False):
699        """
700        Render and show the current data
701        """
702        # If we don't have any data, skip.
703        if data is None:
704            return
705        color_dic = {'H': 'blue', 'D': 'purple', 'N': 'orange',
706                     'O': 'red', 'C': 'green', 'P': 'cyan', 'Other': 'k'}
707        marker = ','
708        m_size = 2
709
710        pos_x = data.pos_x
711        pos_y = data.pos_y
712        pos_z = data.pos_z
713        sld_mx = data.sld_mx
714        sld_my = data.sld_my
715        sld_mz = data.sld_mz
716        pix_symbol = data.pix_symbol
717        sld_tot = numpy.fabs(sld_mx) + numpy.fabs(sld_my) + \
718                  numpy.fabs(sld_mz) + numpy.fabs(data.sld_n)
719        is_nonzero = sld_tot > 0.0
720        is_zero = sld_tot == 0.0
721
722        if data.pix_type == 'atom':
723            marker = 'o'
724            m_size = 3.5
725
726        self.figure.clear()
727        self.figure.subplots_adjust(left=0.1, right=.8, bottom=.1)
728        ax = Axes3D(self.figure)
729        ax.set_xlabel('x ($\A{}$)'.format(data.pos_unit))
730        ax.set_ylabel('z ($\A{}$)'.format(data.pos_unit))
731        ax.set_zlabel('y ($\A{}$)'.format(data.pos_unit))
732
733        # I. Plot null points
734        if is_zero.any():
735            im = ax.plot(pos_x[is_zero], pos_z[is_zero], pos_y[is_zero],
736                           marker, c="y", alpha=0.5, markeredgecolor='y',
737                           markersize=m_size)
738            pos_x = pos_x[is_nonzero]
739            pos_y = pos_y[is_nonzero]
740            pos_z = pos_z[is_nonzero]
741            sld_mx = sld_mx[is_nonzero]
742            sld_my = sld_my[is_nonzero]
743            sld_mz = sld_mz[is_nonzero]
744            pix_symbol = data.pix_symbol[is_nonzero]
745        # II. Plot selective points in color
746        other_color = numpy.ones(len(pix_symbol), dtype='bool')
747        for key in color_dic.keys():
748            chosen_color = pix_symbol == key
749            if numpy.any(chosen_color):
750                other_color = other_color & (chosen_color!=True)
751                color = color_dic[key]
752                im = ax.plot(pos_x[chosen_color], pos_z[chosen_color],
753                         pos_y[chosen_color], marker, c=color, alpha=0.5,
754                         markeredgecolor=color, markersize=m_size, label=key)
755        # III. Plot All others
756        if numpy.any(other_color):
757            a_name = ''
758            if data.pix_type == 'atom':
759                # Get atom names not in the list
760                a_names = [symb for symb in pix_symbol \
761                           if symb not in color_dic.keys()]
762                a_name = a_names[0]
763                for name in a_names:
764                    new_name = ", " + name
765                    if a_name.count(name) == 0:
766                        a_name += new_name
767            # plot in black
768            im = ax.plot(pos_x[other_color], pos_z[other_color],
769                         pos_y[other_color], marker, c="k", alpha=0.5,
770                         markeredgecolor="k", markersize=m_size, label=a_name)
771        if data.pix_type == 'atom':
772            ax.legend(loc='upper left', prop={'size': 10})
773        # IV. Draws atomic bond with grey lines if any
774        if data.has_conect:
775            for ind in range(len(data.line_x)):
776                im = ax.plot(data.line_x[ind], data.line_z[ind],
777                             data.line_y[ind], '-', lw=0.6, c="grey",
778                             alpha=0.3)
779        # V. Draws magnetic vectors
780        if has_arrow and len(pos_x) > 0:
781            def _draw_arrow(input=None, update=None):
782                """
783                draw magnetic vectors w/arrow
784                """
785                max_mx = max(numpy.fabs(sld_mx))
786                max_my = max(numpy.fabs(sld_my))
787                max_mz = max(numpy.fabs(sld_mz))
788                max_m = max(max_mx, max_my, max_mz)
789                try:
790                    max_step = max(data.xstepsize, data.ystepsize, data.zstepsize)
791                except:
792                    max_step = 0
793                if max_step <= 0:
794                    max_step = 5
795                try:
796                    if max_m != 0:
797                        unit_x2 = sld_mx / max_m
798                        unit_y2 = sld_my / max_m
799                        unit_z2 = sld_mz / max_m
800                        # 0.8 is for avoiding the color becomes white=(1,1,1))
801                        color_x = numpy.fabs(unit_x2 * 0.8)
802                        color_y = numpy.fabs(unit_y2 * 0.8)
803                        color_z = numpy.fabs(unit_z2 * 0.8)
804                        x2 = pos_x + unit_x2 * max_step
805                        y2 = pos_y + unit_y2 * max_step
806                        z2 = pos_z + unit_z2 * max_step
807                        x_arrow = numpy.column_stack((pos_x, x2))
808                        y_arrow = numpy.column_stack((pos_y, y2))
809                        z_arrow = numpy.column_stack((pos_z, z2))
810                        colors = numpy.column_stack((color_x, color_y, color_z))
811                        arrows = Arrow3D(self.figure, x_arrow, z_arrow, y_arrow,
812                                        colors, mutation_scale=10, lw=1,
813                                        arrowstyle="->", alpha=0.5)
814                        ax.add_artist(arrows)
815                except:
816                    pass
817                log_msg = "Arrow Drawing completed.\n"
818                logging.info(log_msg)
819            log_msg = "Arrow Drawing is in progress..."
820            logging.info(log_msg)
821
822            # Defer the drawing of arrows to another thread
823            d = threads.deferToThread(_draw_arrow, ax)
824
825        self.figure.canvas.resizing = False
826        self.figure.canvas.draw()
827
828
829class Plotter3D(QtGui.QDialog, Plotter3DWidget):
830    def __init__(self, parent=None, graph_title=''):
831        self.graph_title = graph_title
832        QtGui.QDialog.__init__(self)
833        Plotter3DWidget.__init__(self, manager=parent)
834        self.setWindowTitle(self.graph_title)
835
Note: See TracBrowser for help on using the repository browser.