Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sasview counterpart to sasdata manipulations rewrite #2615

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ jobs:

- name: Fetch sources for sibling projects
run: |
git clone --depth=50 --branch=master https://github.com/SasView/sasdata.git ../sasdata
git clone --depth=50 --branch=46-manipulations-dot-py-rewrite https://github.com/SasView/sasdata.git ../sasdata
git clone --depth=50 --branch=master https://github.com/SasView/sasmodels.git ../sasmodels
git clone --depth=50 --branch=master https://github.com/bumps/bumps.git ../bumps

Expand Down
2 changes: 1 addition & 1 deletion src/sas/qtgui/Calculators/DataOperationUtilityPanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from sas.qtgui.Plotting.PlotterData import Data1D
from sas.qtgui.Plotting.Plotter import PlotterWidget
from sas.qtgui.Plotting.PlotterData import Data2D
from sas.qtgui.Plotting.Plotter2D import Plotter2DWidget
# from sas.qtgui.Plotting.Plotter2D import Plotter2DWidget
import sas.qtgui.Utilities.GuiUtils as GuiUtils

from .UI.DataOperationUtilityUI import Ui_DataOperationUtility
Expand Down
2 changes: 0 additions & 2 deletions src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
from sasdata.dataloader.data_info import Detector, Source
from sas.sascalc.calculator import sas_gen
from sas.qtgui.Plotting.PlotterBase import PlotterBase
from sas.qtgui.Plotting.Plotter2D import Plotter2D
from sas.qtgui.Plotting.Plotter import Plotter
from sas.qtgui.Plotting.Arrow3D import Arrow3D

from sas.qtgui.Plotting.PlotterData import Data1D
Expand Down
108 changes: 67 additions & 41 deletions .../qtgui/Plotting/Slicers/BaseInteractor.py → src/sas/qtgui/Plotting/BaseInteractor.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from abc import ABC, abstractmethod
from typing import Optional, TypeVar, Generic, Any

from PySide6.QtCore import QEvent
from matplotlib.lines import Line2D
from matplotlib.axes import Axes

from sas.qtgui.Plotting.PlotterBase import PlotterBase

# Colours
interface_color = 'black'
disable_color = 'gray'
active_color = 'red'
Expand All @@ -7,24 +17,27 @@
theta_color = 'orange'
profile_colors = [rho_color, mu_color, P_color, theta_color]

class BaseInteractor(object):
PlotterBaseT = TypeVar('PlotterBaseT', bound=PlotterBase)

class BaseInteractor(ABC, Generic[PlotterBaseT]):
"""
Share some functions between the interface interactor and various layer
interactors.

Individual interactors need the following functions:

Abstract methods:
save(ev) - save the current state for later restore
restore() - restore the old state
move(x,y,ev) - move the interactor to position x,y
moveend(ev) - end the drag event
update() - draw the interactors
setParameter(parameter_name, parameter_value) - set a parameter for this interactor
getParameters() - get a dictionary containing the parameters for this interactor


The following are provided by the base class:

connect_markers(markers) - register callbacks for all markers
clear_markers() - remove all items in self.markers
onHilite(ev) - enter/leave event processing
onHighlight(ev) - enter/leave event processing
onLeave(ev) - enter/leave event processing
onClick(ev) - mouse click: calls save()
onRelease(ev) - mouse click ends: calls moveend()
Expand All @@ -39,16 +52,17 @@ class BaseInteractor(object):
markers - list of handles for the interactor

"""
def __init__(self, base, axes, color='black'):
"""
"""
self.base = base
self.axes = axes
self.color = color
self.clickx = None
self.clicky = None
self.markers = []
def __init__(self, base: PlotterBaseT, axes: Axes, color: str='black'):

self.base: PlotterBaseT = base
self.axes: Axes = axes
self.color: str = color

self.clickx: Optional[int] = None
self.clicky: Optional[int] = None
self.markers: list[Line2D] = []

# TODO: Why?
if isinstance(base.data, list):
self.data = self.base.data[0]
else:
Expand All @@ -58,91 +72,95 @@ def clear_markers(self):
"""
Clear old markers and interfaces.
"""
for h in self.markers: h.remove()
for h in self.markers:
h.remove()

if self.markers:
self.base.connect.clear(*self.markers)

self.markers = []

def save(self, ev):
"""
"""
pass
@abstractmethod
def save(self, ev: QEvent):
""" save the current state for later restore """

def restore(self, ev):
"""
"""
pass
@abstractmethod
def restore(self, ev: QEvent):
""" restore the old state """

def move(self, x, y, ev):
"""
"""
pass
@abstractmethod
def move(self, x, y, ev: QEvent):
""" move the interactor to position x,y """

def moveend(self, ev):
"""
"""
pass
@abstractmethod
def moveend(self, ev: QEvent):
""" end the drag event """

def connect_markers(self, markers):
def connect_markers(self, markers: list[Line2D]):
"""
Connect markers to callbacks
"""

for h in markers:
connect = self.base.connect
connect('enter', h, self.onHilite)
connect('enter', h, self.onHighlight)
connect('leave', h, self.onLeave)
connect('click', h, self.onClick)
connect('release', h, self.onRelease)
connect('drag', h, self.onDrag)
connect('key', h, self.onKey)

def onHilite(self, ev):
def onHighlight(self, ev: QEvent):
"""
Hilite the artist reporting the event, indicating that it is
Highlight the artist reporting the event, indicating that it is
ready to receive a click.
"""
ev.artist.set_color(active_color)
self.base.draw()
return True

def onLeave(self, ev):
def onLeave(self, ev: QEvent):
"""
Restore the artist to the original colour when the cursor leaves.
"""
ev.artist.set_color(self.color)
self.base.draw()
return True

def onClick(self, ev):
def onClick(self, ev: QEvent):
"""
Prepare to move the artist. Calls save() to preserve the state for
later restore().
"""
self.clickx, self.clicky = ev.xdata, ev.ydata
self.save(ev)

return True

def onRelease(self, ev):
def onRelease(self, ev: QEvent):
"""
Mouse release
"""
self.moveend(ev)
return True

def onDrag(self, ev):
def onDrag(self, ev: QEvent):
"""
Move the artist. Calls move() to update the state, or restore() if
the mouse leaves the window.
"""
inside, _ = self.axes.contains(ev)

if inside:
self.clickx, self.clicky = ev.xdata, ev.ydata
self.move(ev.xdata, ev.ydata, ev)

else:
self.restore(ev)

return True

def onKey(self, ev):
def onKey(self, ev: QEvent):
"""
Respond to keyboard events. Arrow keys move the widget. Escape
restores it to the position before the last click.
Expand All @@ -151,6 +169,7 @@ def onKey(self, ev):
"""
if ev.key == 'escape':
self.restore(ev)

elif ev.key in ['up', 'down', 'right', 'left']:
dx, dy = self.dpixel(self.clickx, self.clicky, nudge=ev.control)
if ev.key == 'up':
Expand All @@ -161,24 +180,31 @@ def onKey(self, ev):
self.clickx += dx
else: self.clickx -= dx
self.move(self.clickx, self.clicky, ev)

else:
return False

self.base.update()

return True

def dpixel(self, x, y, nudge=False):
def dpixel(self, x: float, y: float, nudge=False) -> tuple[float, float]:
"""
Return the step size in data coordinates for a small
step in screen coordinates. If nudge is False (default)
the step size is one pixel. If nudge is True, the step
size is 0.2 pixels.
"""
ax = self.axes

px, py = ax.transData.inverse_xy_tup((x, y))

if nudge:
nx, ny = ax.transData.xy_tup((px + 0.2, py + 0.2))
else:
nx, ny = ax.transData.xy_tup((px + 1.0, py + 1.0))
dx, dy = nx - x, ny - y

return dx, dy


3 changes: 0 additions & 3 deletions src/sas/qtgui/Plotting/LinearFit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"""
import re
import numpy
from numbers import Number
from typing import Optional
from PySide6 import QtCore
from PySide6 import QtGui
from PySide6 import QtWidgets
Expand All @@ -18,7 +16,6 @@
import sas.qtgui.Utilities.GuiUtils as GuiUtils

# Local UI
from sas.qtgui.UI import main_resources_rc
from sas.qtgui.Plotting.UI.LinearFitUI import Ui_LinearFitUI


Expand Down
10 changes: 5 additions & 5 deletions src/sas/qtgui/Plotting/Masks/BoxMask.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import numpy as np

from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicers.BoxSum import PointInteractor
from sas.qtgui.Plotting.Slicers.BoxSum import VerticalDoubleLine
from sas.qtgui.Plotting.Slicers.BoxSum import HorizontalDoubleLine
from sas.qtgui.Plotting.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicing.Slicers.BoxSum import PointInteractor
from sas.qtgui.Plotting.Slicing.Slicers.BoxSum import VerticalDoubleLine
from sas.qtgui.Plotting.Slicing.Slicers.BoxSum import HorizontalDoubleLine


class BoxMask(BaseInteractor):
Expand Down Expand Up @@ -138,7 +138,7 @@ def _post_data(self):
Get the limits of the boxsum and compute the sum of the pixel
contained in that region and the error on that sum
"""
from sasdata.data_util.manipulations import Boxcut
from sasdata.data_util.averaging import Boxcut
# # Data 2D for which the pixel will be summed
data = self.data
mask = data.mask
Expand Down
6 changes: 3 additions & 3 deletions src/sas/qtgui/Plotting/Masks/CircularMask.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy

from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicers.AnnulusSlicer import RingInteractor
from sas.qtgui.Plotting.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicing.Slicers.AnnulusSlicer import RingInteractor

class CircularMask(BaseInteractor):
"""
Expand Down Expand Up @@ -86,7 +86,7 @@ def _post_data(self):
if data is None:
return
mask = data.mask
from sasdata.data_util.manipulations import Ringcut
from sasdata.data_util.averaging import Ringcut

rmin = 0
rmax = numpy.fabs(self.outer_circle.get_radius())
Expand Down
16 changes: 10 additions & 6 deletions src/sas/qtgui/Plotting/Masks/SectorMask.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
"""
import numpy as np

from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicers.SectorSlicer import SideInteractor
from sas.qtgui.Plotting.Slicers.SectorSlicer import LineInteractor
from matplotlib.axes import Axes
from sas.qtgui.Plotting.Plotter2D import Plotter2D

class SectorMask(BaseInteractor):
from sas.qtgui.Plotting.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.Slicing.Slicers.SectorSlicer import SideInteractor
from sas.qtgui.Plotting.Slicing.Slicers.SectorSlicer import LineInteractor


class SectorMask(BaseInteractor[Plotter2D]):
"""
Draw a sector slicer.Allow to find the data 2D inside of the sector lines
"""
def __init__(self, base, axes, color='gray', zorder=3, side=False):
def __init__(self, base: Plotter2D, axes: Axes, color='gray', zorder=3, side=False):
"""
"""
BaseInteractor.__init__(self, base, axes, color=color)
Expand Down Expand Up @@ -114,7 +118,7 @@ def _post_data(self):
if self.data is None:
return
## Averaging
from sasdata.data_util.manipulations import Sectorcut
from sasdata.data_util.averaging import Sectorcut
phimin = -self.left_line.phi + self.main_line.theta
phimax = self.left_line.phi + self.main_line.theta

Expand Down
Loading
Loading