Changeset e20870bc in sasview for src/sas/qtgui/Plotting


Ignore:
Timestamp:
Jun 27, 2018 5:33:52 AM (6 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
b1a7a81
Parents:
c5e0d84
git-author:
Piotr Rozyczko <rozyczko@…> (05/31/18 07:15:47)
git-committer:
Piotr Rozyczko <rozyczko@…> (06/27/18 05:33:52)
Message:

Masking dialog for fitting

Location:
src/sas/qtgui/Plotting
Files:
5 added
9 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Plotting/MaskEditor.py

    r4992ff2 re20870bc  
    1 from PyQt5 import QtCore 
    2 from PyQt5 import QtGui 
     1from functools import partial 
     2import copy 
     3import numpy as np 
     4 
    35from PyQt5 import QtWidgets 
    46 
     
    1012from sas.qtgui.Plotting.Plotter2D import Plotter2DWidget 
    1113 
     14from sas.qtgui.Plotting.Masks.SectorMask import SectorMask 
     15from sas.qtgui.Plotting.Masks.BoxMask import BoxMask 
     16from sas.qtgui.Plotting.Masks.CircularMask import CircularMask 
     17 
     18 
    1219class MaskEditor(QtWidgets.QDialog, Ui_MaskEditorUI): 
    1320    def __init__(self, parent=None, data=None): 
    1421        super(MaskEditor, self).__init__() 
    1522 
    16         assert(isinstance(data, Data2D)) 
     23        assert isinstance(data, Data2D) 
    1724 
    1825        self.setupUi(self) 
    1926 
    2027        self.data = data 
     28        self.parent = parent 
    2129        filename = data.name 
     30 
     31        self.current_slicer = None 
     32        self.slicer_mask = None 
     33 
    2234        self.setWindowTitle("Mask Editor for %s" % filename) 
    2335 
    2436        self.plotter = Plotter2DWidget(self, manager=parent, quickplot=True) 
    2537        self.plotter.data = self.data 
     38        self.slicer_z = 0 
     39        self.default_mask = copy.deepcopy(data.mask) 
    2640 
    2741        layout = QtWidgets.QHBoxLayout() 
     
    3145 
    3246        self.plotter.plot() 
    33  
     47        self.subplot = self.plotter.ax 
     48 
     49        # update mask 
     50        self.updateMask(self.default_mask) 
     51 
     52        self.initializeSignals() 
     53 
     54    def initializeSignals(self): 
     55        """ 
     56        Attach slots to signals from radio boxes 
     57        """ 
     58        self.rbWings.toggled.connect(partial(self.onMask, slicer=SectorMask, inside=True)) 
     59        self.rbCircularDisk.toggled.connect(partial(self.onMask, slicer=CircularMask, inside=True)) 
     60        self.rbRectangularDisk.toggled.connect(partial(self.onMask, slicer=BoxMask, inside=True)) 
     61        self.rbDoubleWingWindow.toggled.connect(partial(self.onMask, slicer=SectorMask, inside=False)) 
     62        self.rbCircularWindow.toggled.connect(partial(self.onMask, slicer=CircularMask, inside=False)) 
     63        self.rbRectangularWindow.toggled.connect(partial(self.onMask, slicer=BoxMask, inside=False)) 
     64 
     65        # Button groups defined so we can uncheck all buttons programmatically 
     66        self.buttonGroup = QtWidgets.QButtonGroup() 
     67        self.buttonGroup.addButton(self.rbWings) 
     68        self.buttonGroup.addButton(self.rbCircularDisk) 
     69        self.buttonGroup.addButton(self.rbRectangularDisk) 
     70        self.buttonGroup.addButton(self.rbDoubleWingWindow) 
     71        self.buttonGroup.addButton(self.rbCircularWindow) 
     72        self.buttonGroup.addButton(self.rbRectangularWindow) 
     73 
     74        # Push buttons 
     75        self.cmdAdd.clicked.connect(self.onAdd) 
     76        self.cmdReset.clicked.connect(self.onReset) 
     77        self.cmdClear.clicked.connect(self.onClear) 
     78 
     79    def emptyRadioButtons(self): 
     80        """ 
     81        Uncheck all buttons without them firing signals causing unnecessary slicer updates 
     82        """ 
     83        self.buttonGroup.setExclusive(False) 
     84        self.rbWings.blockSignals(True) 
     85        self.rbWings.setChecked(False) 
     86        self.rbWings.blockSignals(False) 
     87 
     88        self.rbCircularDisk.blockSignals(True) 
     89        self.rbCircularDisk.setChecked(False) 
     90        self.rbCircularDisk.blockSignals(False) 
     91 
     92        self.rbRectangularDisk.blockSignals(True) 
     93        self.rbRectangularDisk.setChecked(False) 
     94        self.rbRectangularDisk.blockSignals(False) 
     95 
     96        self.rbDoubleWingWindow.blockSignals(True) 
     97        self.rbDoubleWingWindow.setChecked(False) 
     98        self.rbDoubleWingWindow.blockSignals(False) 
     99 
     100        self.rbCircularWindow.blockSignals(True) 
     101        self.rbCircularWindow.setChecked(False) 
     102        self.rbCircularWindow.blockSignals(False) 
     103 
     104        self.rbRectangularWindow.blockSignals(True) 
     105        self.rbRectangularWindow.setChecked(False) 
     106        self.rbRectangularWindow.blockSignals(False) 
     107        self.buttonGroup.setExclusive(True) 
     108 
     109    def setSlicer(self, slicer): 
     110        """ 
     111        Clear the previous slicer and create a new one. 
     112        slicer: slicer class to create 
     113        """ 
     114        # Clear current slicer 
     115        if self.current_slicer is not None: 
     116            self.current_slicer.clear() 
     117        # Create a new slicer 
     118        self.slicer_z += 1 
     119        self.current_slicer = slicer(self, self.ax, zorder=self.slicer_z) 
     120        self.ax.set_ylim(self.data.ymin, self.data.ymax) 
     121        self.ax.set_xlim(self.data.xmin, self.data.xmax) 
     122        # Draw slicer 
     123        self.figure.canvas.draw() 
     124        self.current_slicer.update() 
     125 
     126    def onMask(self, slicer=None, inside=True): 
     127        """ 
     128        Clear the previous mask and create a new one. 
     129        """ 
     130        self.clearSlicer() 
     131        # modifying data in-place 
     132        self.slicer_z += 1 
     133 
     134        self.current_slicer = slicer(self.plotter, self.plotter.ax, zorder=self.slicer_z, side=inside) 
     135 
     136        self.plotter.ax.set_ylim(self.data.ymin, self.data.ymax) 
     137        self.plotter.ax.set_xlim(self.data.xmin, self.data.xmax) 
     138 
     139        self.plotter.canvas.draw() 
     140 
     141        self.slicer_mask = self.current_slicer.update() 
     142 
     143    def update(self): 
     144        """ 
     145        Redraw the canvas 
     146        """ 
     147        self.plotter.draw() 
     148 
     149    def onAdd(self): 
     150        """ 
     151        Generate required mask and modify underlying DATA 
     152        """ 
     153        if self.current_slicer is None: 
     154            return 
     155        data = Data2D() 
     156        data = self.data 
     157        self.slicer_mask = self.current_slicer.update() 
     158        data.mask = self.data.mask & self.slicer_mask 
     159        self.updateMask(data.mask) 
     160        self.emptyRadioButtons() 
     161 
     162    def onClear(self): 
     163        """ 
     164        Remove the current mask(s) 
     165        """ 
     166        self.slicer_z += 1 
     167        self.clearSlicer() 
     168        self.current_slicer = BoxMask(self.plotter, self.plotter.ax, 
     169                                      zorder=self.slicer_z, side=True) 
     170        self.plotter.ax.set_ylim(self.data.ymin, self.data.ymax) 
     171        self.plotter.ax.set_xlim(self.data.xmin, self.data.xmax) 
     172 
     173        self.data.mask = copy.deepcopy(self.default_mask) 
     174        # update mask plot 
     175        self.updateMask(self.data.mask) 
     176        self.emptyRadioButtons() 
     177 
     178    def onReset(self): 
     179        """ 
     180        Removes all the masks from data 
     181        """ 
     182        self.slicer_z += 1 
     183        self.clearSlicer() 
     184        self.current_slicer = BoxMask(self.plotter, self.plotter.ax, 
     185                                      zorder=self.slicer_z, side=True) 
     186        self.plotter.ax.set_ylim(self.data.ymin, self.data.ymax) 
     187        self.plotter.ax.set_xlim(self.data.xmin, self.data.xmax) 
     188        mask = np.ones(len(self.data.mask), dtype=bool) 
     189        self.data.mask = mask 
     190        # update mask plot 
     191        self.updateMask(mask) 
     192        self.emptyRadioButtons() 
     193 
     194    def clearSlicer(self): 
     195        """ 
     196        Clear the slicer on the plot 
     197        """ 
     198        if self.current_slicer is None: 
     199            return 
     200 
     201        self.current_slicer.clear() 
     202        self.plotter.draw() 
     203        self.current_slicer = None 
     204 
     205    def updateMask(self, mask): 
     206        """ 
     207        Respond to changes in masking 
     208        """ 
     209        # the case of liitle numbers of True points 
     210        if len(mask[mask]) < 10 and self.data is not None: 
     211            self.data.mask = copy.deepcopy(self.mask) 
     212        else: 
     213            self.mask = mask 
     214        # make temperary data to plot 
     215        temp_mask = np.zeros(len(mask)) 
     216        temp_data = copy.deepcopy(self.data) 
     217        # temp_data default is None 
     218        # This method is to distinguish between masked point and data point = 0. 
     219        temp_mask = temp_mask / temp_mask 
     220        temp_mask[mask] = temp_data.data[mask] 
     221 
     222        temp_data.data[mask == False] = temp_mask[mask == False] 
     223 
     224        if self.current_slicer is not None: 
     225            self.current_slicer.clear() 
     226            self.current_slicer = None 
     227 
     228        # modify imshow data 
     229        self.plotter.plot(data=temp_data) 
     230        self.plotter.draw() 
     231 
     232        self.subplot = self.plotter.ax 
  • src/sas/qtgui/Plotting/Plotter2D.py

    rd6b8a1d re20870bc  
    6161        self.vmin = None 
    6262        self.vmax = None 
     63        self.im = None 
    6364 
    6465        self.manager = manager 
     
    439440                zmin_temp = self.vmin 
    440441                zmax_temp = self.vmax 
    441  
    442             im = self.ax.imshow(output, interpolation='nearest', 
     442            if self.im is not None: 
     443                self.im.set_data(output) 
     444            else: 
     445                self.im = self.ax.imshow(output, interpolation='nearest', 
    443446                                # origin='lower', 
    444447                                vmin=zmin_temp, vmax=zmax_temp, 
     
    459462            if cbax is None: 
    460463                ax.set_frame_on(False) 
    461                 cb = self.figure.colorbar(im, shrink=0.8, aspect=20) 
     464                cb = self.figure.colorbar(self.im, shrink=0.8, aspect=20) 
    462465            else: 
    463                 cb = self.figure.colorbar(im, cax=cbax) 
    464  
    465             cb.update_bruteforce(im) 
     466                cb = self.figure.colorbar(self.im, cax=cbax) 
     467 
     468            cb.update_bruteforce(self.im) 
    466469            cb.set_label('$' + self.scale + '$') 
    467470 
  • src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py

    r4992ff2 re20870bc  
    382382        self.set_cursor(x, self._inner_mouse_y) 
    383383 
    384 class CircularMask(BaseInteractor): 
    385     """ 
    386      Draw a ring Given a radius 
    387     """ 
    388     def __init__(self, base, axes, color='grey', zorder=3, side=None): 
    389         """ 
    390         :param: the color of the line that defined the ring 
    391         :param r: the radius of the ring 
    392         :param sign: the direction of motion the the marker 
    393         """ 
    394         BaseInteractor.__init__(self, base, axes, color=color) 
    395         self.markers = [] 
    396         self.axes = axes 
    397         self.base = base 
    398         self.is_inside = side 
    399         self.qmax = min(numpy.fabs(self.base.data.xmax), 
    400                         numpy.fabs(self.base.data.xmin))  # must be positive 
    401         self.connect = self.base.connect 
    402  
    403         # Cursor position of Rings (Left(-1) or Right(1)) 
    404         self.xmaxd = self.base.data.xmax 
    405         self.xmind = self.base.data.xmin 
    406  
    407         if (self.xmaxd + self.xmind) > 0: 
    408             self.sign = 1 
    409         else: 
    410             self.sign = -1 
    411         # Inner circle 
    412         self.outer_circle = RingInteractor(self, self.axes, 'blue', 
    413                                            zorder=zorder + 1, r=self.qmax / 1.8, 
    414                                            sign=self.sign) 
    415         self.outer_circle.qmax = self.qmax * 1.2 
    416         self.update() 
    417         self._post_data() 
    418  
    419     def set_layer(self, n): 
    420         """ 
    421         Allow adding plot to the same panel 
    422         :param n: the number of layer 
    423         """ 
    424         self.layernum = n 
    425         self.update() 
    426  
    427     def clear(self): 
    428         """ 
    429         Clear the slicer and all connected events related to this slicer 
    430         """ 
    431         self.clear_markers() 
    432         self.outer_circle.clear() 
    433         self.base.connect.clearall() 
    434  
    435     def update(self): 
    436         """ 
    437         Respond to changes in the model by recalculating the profiles and 
    438         resetting the widgets. 
    439         """ 
    440         # Update locations 
    441         self.outer_circle.update() 
    442         self._post_data() 
    443         out = self._post_data() 
    444         return out 
    445  
    446     def save(self, ev): 
    447         """ 
    448         Remember the roughness for this layer and the next so that we 
    449         can restore on Esc. 
    450         """ 
    451         self.outer_circle.save(ev) 
    452  
    453     def _post_data(self): 
    454         """ 
    455         Uses annulus parameters to plot averaged data into 1D data. 
    456  
    457         :param nbins: the number of points to plot 
    458  
    459         """ 
    460         # Data to average 
    461         data = self.base.data 
    462  
    463         # If we have no data, just return 
    464         if data is None: 
    465             return 
    466         mask = data.mask 
    467         from sas.sascalc.dataloader.manipulations import Ringcut 
    468  
    469         rmin = 0 
    470         rmax = numpy.fabs(self.outer_circle.get_radius()) 
    471  
    472         # Create the data1D Q average of data2D 
    473         mask = Ringcut(r_min=rmin, r_max=rmax) 
    474  
    475         if self.is_inside: 
    476             out = (mask(data) == False) 
    477         else: 
    478             out = (mask(data)) 
    479         return out 
    480  
    481  
    482     def moveend(self, ev): 
    483         """ 
    484         Called when any dragging motion ends. 
    485         Post an event (type =SlicerParameterEvent) 
    486         to plotter 2D with a copy  slicer parameters 
    487         Call  _post_data method 
    488         """ 
    489         self.base.thaw_axes() 
    490         # create a 1D data plot 
    491         self._post_data() 
    492  
    493     def restore(self): 
    494         """ 
    495         Restore the roughness for this layer. 
    496         """ 
    497         self.outer_circle.restore() 
    498  
    499     def move(self, x, y, ev): 
    500         """ 
    501         Process move to a new position, making sure that the move is allowed. 
    502         """ 
    503         pass 
    504  
    505     def set_cursor(self, x, y): 
    506         pass 
    507  
    508     def getParams(self): 
    509         """ 
    510         Store a copy of values of parameters of the slicer into a dictionary. 
    511  
    512         :return params: the dictionary created 
    513  
    514         """ 
    515         params = {} 
    516         params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x) 
    517         return params 
    518  
    519     def setParams(self, params): 
    520         """ 
    521         Receive a dictionary and reset the slicer with values contained 
    522         in the values of the dictionary. 
    523  
    524         :param params: a dictionary containing name of slicer parameters and 
    525             values the user assigned to the slicer. 
    526         """ 
    527         outer = numpy.fabs(params["outer_radius"]) 
    528         # Update the picture 
    529         self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y) 
    530         # Post the data given the nbins entered by the user 
    531         self._post_data() 
    532  
    533     def draw(self): 
    534         self.base.update() 
    535  
     384 
  • src/sas/qtgui/Plotting/Slicers/Arc.py

    • Property mode changed from 100755 to 100644
    rd744767 re20870bc  
    22    Arc slicer for 2D data 
    33""" 
    4 import math 
     4import numpy as np 
    55 
    6 from .BaseInteractor import BaseInteractor 
     6from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor 
    77 
    88class ArcInteractor(BaseInteractor): 
     
    1111    """ 
    1212    def __init__(self, base, axes, color='black', zorder=5, r=1.0, 
    13                  theta1=math.pi / 8, theta2=math.pi / 4): 
     13                 theta1=np.pi / 8, theta2=np.pi / 4): 
    1414        BaseInteractor.__init__(self, base, axes, color=color) 
    1515        self.markers = [] 
     
    5555            Return arc radius 
    5656        """ 
    57         radius = math.sqrt(math.pow(self._mouse_x, 2) + \ 
    58                            math.pow(self._mouse_y, 2)) 
     57        radius = np.sqrt(np.power(self._mouse_x, 2) + \ 
     58                           np.power(self._mouse_y, 2)) 
    5959        return radius 
    6060 
     
    7575            self.theta2 = theta2 
    7676        while self.theta2 < self.theta1: 
    77             self.theta2 += (2 * math.pi) 
    78         while self.theta2 >= (self.theta1 + 2 * math.pi): 
    79             self.theta2 -= (2 * math.pi) 
    80         npts = int((self.theta2 - self.theta1) / (math.pi / 120)) 
     77            self.theta2 += (2 * np.pi) 
     78        while self.theta2 >= (self.theta1 + 2 * np.pi): 
     79            self.theta2 -= (2 * np.pi) 
     80        npts = int((self.theta2 - self.theta1) / (np.pi / 120)) 
    8181 
    8282        if r is None: 
    83             self.radius = math.sqrt(math.pow(self._mouse_x, 2) + \ 
    84                                      math.pow(self._mouse_y, 2)) 
     83            self.radius = np.sqrt(np.power(self._mouse_x, 2) + \ 
     84                                     np.power(self._mouse_y, 2)) 
    8585        else: 
    8686            self.radius = r 
    8787        for i in range(self.npts): 
    8888            phi = (self.theta2 - self.theta1) / (self.npts - 1) * i + self.theta1 
    89             xval = 1.0 * self.radius * math.cos(phi) 
    90             yval = 1.0 * self.radius * math.sin(phi) 
     89            xval = 1.0 * self.radius * np.cos(phi) 
     90            yval = 1.0 * self.radius * np.sin(phi) 
    9191 
    9292            x.append(xval) 
  • src/sas/qtgui/Plotting/Slicers/AzimutSlicer.py

    • Property mode changed from 100755 to 100644
    rb3e8629 re20870bc  
    44# TODO: NEED MAJOR REFACTOR 
    55# 
    6 import math 
    7 from .BaseInteractor import BaseInteractor 
     6import numpy as np 
     7from sas.qtgui.Plotting.Slicers.Arc import ArcInteractor 
     8from sas.qtgui.Plotting.Slicers.RadiusInteractor import RadiusInteractor 
     9from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor 
    810 
    911class SectorInteractor(BaseInteractor): 
     
    2224        # # Number of points on the plot 
    2325        self.nbins = 20 
    24         theta1 = 2 * math.pi / 3 
    25         theta2 = -2 * math.pi / 3 
     26        theta1 = 2 * np.pi / 3 
     27        theta2 = -2 * np.pi / 3 
    2628 
    2729        # Inner circle 
    28         from .Arc import ArcInteractor 
    2930        self.inner_circle = ArcInteractor(self, self.base.subplot, 
    3031                                          zorder=zorder, 
     
    4041        self.outer_circle.qmax = self.qmax * 1.2 
    4142        # self.outer_circle.set_cursor(self.base.qmax/1.8, 0) 
    42         from Edge import RadiusInteractor 
    4343        self.right_edge = RadiusInteractor(self, self.base.subplot, 
    4444                                           zorder=zorder + 1, 
     
    132132            phimin = self.left_edge.get_angle() 
    133133            phimax = self.right_edge.get_angle() 
    134         # print "phimin, phimax, rmin ,rmax",math.degrees(phimin), 
    135         # math.degrees(phimax), rmin ,rmax 
    136         # from sas.sascalc.dataloader.manipulations import SectorQ 
    137134 
    138135        sect = new_sector(r_min=rmin, r_max=rmax, 
  • src/sas/qtgui/Plotting/Slicers/BoxSlicer.py

    r4992ff2 re20870bc  
    11import numpy 
    22 
    3 from .BaseInteractor import BaseInteractor 
     3from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor 
    44from sas.qtgui.Plotting.PlotterData import Data1D 
    55import sas.qtgui.Utilities.GuiUtils as GuiUtils 
  • src/sas/qtgui/Plotting/Slicers/BoxSum.py

    rfbfc488 re20870bc  
    88from sas.qtgui.Utilities.GuiUtils import formatNumber, toDouble 
    99 
    10 from .BaseInteractor import BaseInteractor 
     10from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor 
    1111from sas.sascalc.dataloader.manipulations import Boxavg 
    1212from sas.sascalc.dataloader.manipulations import Boxsum 
  • src/sas/qtgui/Plotting/Slicers/SectorSlicer.py

    rd6b8a1d re20870bc  
    55import logging 
    66 
    7 from .BaseInteractor import BaseInteractor 
     7from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor 
    88from sas.qtgui.Plotting.PlotterData import Data1D 
    99import sas.qtgui.Utilities.GuiUtils as GuiUtils 
     
    283283        self.markers = [] 
    284284        self.axes = axes 
     285        self.color = color 
    285286        # compute the value of the angle between the current line and 
    286287        # the x-axis 
     
    438439        if self.phi > numpy.pi: 
    439440            self.phi = 2 * numpy.pi - numpy.fabs(self.theta2 - self.theta) 
    440         self.base.base.update() 
     441        #self.base.base.update() 
     442        self.base.update() 
    441443 
    442444    def set_cursor(self, x, y): 
     
    464466 
    465467        self.markers = [] 
     468        self.color = color 
    466469        self.axes = axes 
    467470        self.save_theta = theta 
     
    541544        self.theta = numpy.arctan2(y, x) 
    542545        self.has_move = True 
    543         self.base.base.update() 
     546        #self.base.base.update() 
     547        self.base.update() 
    544548 
    545549    def set_cursor(self, x, y): 
  • src/sas/qtgui/Plotting/UI/MaskEditorUI.ui

    r3010f68 re20870bc  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>642</width> 
    10     <height>431</height> 
     9    <width>687</width> 
     10    <height>507</height> 
    1111   </rect> 
    1212  </property> 
     
    104104      </sizepolicy> 
    105105     </property> 
     106     <property name="toolTip"> 
     107      <string>Clear all the masks</string> 
     108     </property> 
    106109     <property name="text"> 
    107110      <string>Reset</string> 
     
    116119       <verstretch>0</verstretch> 
    117120      </sizepolicy> 
     121     </property> 
     122     <property name="toolTip"> 
     123      <string>Clear recent masks</string> 
    118124     </property> 
    119125     <property name="text"> 
Note: See TracChangeset for help on using the changeset viewer.