import numpy
from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
from sas.qtgui.Plotting.PlotterData import Data1D
import sas.qtgui.Utilities.GuiUtils as GuiUtils
from sas.qtgui.Plotting.SlicerModel import SlicerModel
[docs]class BoxInteractor(BaseInteractor, SlicerModel):
"""
BoxInteractor define a rectangle that return data1D average of Data2D
in a rectangle area defined by -x, x ,y, -y
"""
[docs] def __init__(self, base, axes, item=None, color='black', zorder=3):
BaseInteractor.__init__(self, base, axes, color=color)
SlicerModel.__init__(self)
# Class initialization
self.markers = []
self.axes = axes
self._item = item
# connecting artist
self.connect = self.base.connect
# which direction is the preferred interaction direction
self.direction = None
# determine x y values
self.x = 0.5 * min(numpy.fabs(self.data.xmax),
numpy.fabs(self.data.xmin))
self.y = 0.5 * min(numpy.fabs(self.data.xmax),
numpy.fabs(self.data.xmin))
# when reach qmax reset the graph
self.qmax = max(self.data.xmax, self.data.xmin,
self.data.ymax, self.data.ymin)
# Number of points on the plot
self.nbins = 100
# If True, I(|Q|) will be return, otherwise,
# negative q-values are allowed
self.fold = True
# reference of the current Slab averaging
self.averager = None
# Create vertical and horizaontal lines for the rectangle
self.vertical_lines = VerticalLines(self,
self.axes,
color='blue',
zorder=zorder,
y=self.y,
x=self.x)
self.vertical_lines.qmax = self.qmax
self.horizontal_lines = HorizontalLines(self,
self.axes,
color='green',
zorder=zorder,
x=self.x,
y=self.y)
self.horizontal_lines.qmax = self.qmax
# draw the rectangle and plost the data 1D resulting
# of averaging data2D
self.update()
self._post_data()
self.setModelFromParams()
[docs] def update_and_post(self):
"""
Update the slicer and plot the resulting data
"""
self.update()
self._post_data()
[docs] def set_layer(self, n):
"""
Allow adding plot to the same panel
:param n: the number of layer
"""
self.layernum = n
self.update()
[docs] def clear(self):
"""
Clear the slicer and all connected events related to this slicer
"""
self.averager = None
self.clear_markers()
self.horizontal_lines.clear()
self.vertical_lines.clear()
self.base.connect.clearall()
[docs] def update(self):
"""
Respond to changes in the model by recalculating the profiles and
resetting the widgets.
"""
# #Update the slicer if an horizontal line is dragged
if self.horizontal_lines.has_move:
self.horizontal_lines.update()
self.vertical_lines.update(y=self.horizontal_lines.y)
# #Update the slicer if a vertical line is dragged
if self.vertical_lines.has_move:
self.vertical_lines.update()
self.horizontal_lines.update(x=self.vertical_lines.x)
[docs] def save(self, ev):
"""
Remember the roughness for this layer and the next so that we
can restore on Esc.
"""
self.vertical_lines.save(ev)
self.horizontal_lines.save(ev)
[docs] def _post_data(self):
pass
[docs] def post_data(self, new_slab=None, nbins=None, direction=None):
"""
post data averaging in Qx or Qy given new_slab type
:param new_slab: slicer that determine with direction to average
:param nbins: the number of points plotted when averaging
:param direction: the direction of averaging
"""
if self.direction is None:
self.direction = direction
x_min = -1 * numpy.fabs(self.vertical_lines.x)
x_max = numpy.fabs(self.vertical_lines.x)
y_min = -1 * numpy.fabs(self.horizontal_lines.y)
y_max = numpy.fabs(self.horizontal_lines.y)
if nbins is not None:
self.nbins = nbins
if self.averager is None:
if new_slab is None:
msg = "post data:cannot average , averager is empty"
raise ValueError(msg)
self.averager = new_slab
if self.direction == "X":
if self.fold:
x_low = 0
else:
x_low = numpy.fabs(x_min)
bin_width = (x_max + x_low) / self.nbins
elif self.direction == "Y":
if self.fold:
y_low = 0
else:
y_low = numpy.fabs(y_min)
bin_width = (y_max + y_low) / self.nbins
else:
msg = "post data:no Box Average direction was supplied"
raise ValueError(msg)
# # Average data2D given Qx or Qy
box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
bin_width=bin_width)
box.fold = self.fold
boxavg = box(self.data)
# 3 Create Data1D to plot
if hasattr(boxavg, "dxl"):
dxl = boxavg.dxl
else:
dxl = None
if hasattr(boxavg, "dxw"):
dxw = boxavg.dxw
else:
dxw = None
new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy)
new_plot.dxl = dxl
new_plot.dxw = dxw
new_plot.name = str(self.averager.__name__) + \
"(" + self.data.name + ")"
new_plot.title = str(self.averager.__name__) + \
"(" + self.data.name + ")"
new_plot.source = self.data.source
new_plot.interactive = True
new_plot.detector = self.data.detector
# If the data file does not tell us what the axes are, just assume...
new_plot.xaxis("\\rm{Q}", "A^{-1}")
new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
data = self.data
if hasattr(data, "scale") and data.scale == 'linear' and \
self.data.name.count("Residuals") > 0:
new_plot.ytransform = 'y'
new_plot.yaxis("\\rm{Residuals} ", "/")
new_plot.id = (self.averager.__name__) + self.data.name
new_plot.group_id = new_plot.id
new_plot.is_data = True
item = self._item
if self._item.parent() is not None:
item = self._item.parent()
GuiUtils.updateModelItemWithPlot(item, new_plot, new_plot.id)
self.base.manager.communicator.forcePlotDisplaySignal.emit([item, new_plot])
if self.update_model:
self.setModelFromParams()
self.draw()
[docs] def moveend(self, ev):
"""
Called after a dragging event.
Post the slicer new parameters and creates a new Data1D
corresponding to the new average
"""
self._post_data()
[docs] def restore(self):
"""
Restore the roughness for this layer.
"""
self.horizontal_lines.restore()
self.vertical_lines.restore()
[docs] def move(self, x, y, ev):
"""
Process move to a new position, making sure that the move is allowed.
"""
pass
[docs] def set_cursor(self, x, y):
pass
[docs] def getParams(self):
"""
Store a copy of values of parameters of the slicer into a dictionary.
:return params: the dictionary created
"""
params = {}
params["x_max"] = numpy.fabs(self.vertical_lines.x)
params["y_max"] = numpy.fabs(self.horizontal_lines.y)
params["nbins"] = self.nbins
params["fold"] = self.fold
return params
[docs] def setParams(self, params):
"""
Receive a dictionary and reset the slicer with values contained
in the values of the dictionary.
:param params: a dictionary containing name of slicer parameters and
values the user assigned to the slicer.
"""
self.x = float(numpy.fabs(params["x_max"]))
self.y = float(numpy.fabs(params["y_max"]))
self.nbins = params["nbins"]
self.fold = params["fold"]
self.horizontal_lines.update(x=self.x, y=self.y)
self.vertical_lines.update(x=self.x, y=self.y)
self.post_data(nbins=None)
[docs] def draw(self):
"""
"""
self.base.draw()
[docs]class HorizontalLines(BaseInteractor):
"""
Draw 2 Horizontal lines centered on (0,0) that can move
on the x- direction and in opposite direction
"""
[docs] def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
"""
"""
BaseInteractor.__init__(self, base, axes, color=color)
# Class initialization
self.markers = []
self.axes = axes
# Saving the end points of two lines
self.x = x
self.save_x = x
self.y = y
self.save_y = y
# Creating a marker
# Inner circle marker
self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
marker='s', markersize=10,
color=self.color, alpha=0.6,
pickradius=5, label="pick",
zorder=zorder,
visible=True)[0]
# Define 2 horizontal lines
self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
linestyle='-', marker='',
color=self.color, visible=True)[0]
self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
linestyle='-', marker='',
color=self.color, visible=True)[0]
# Flag to check the motion of the lines
self.has_move = False
# Connecting markers to mouse events and draw
self.connect_markers([self.top_line, self.inner_marker])
self.update()
[docs] def set_layer(self, n):
"""
Allow adding plot to the same panel
:param n: the number of layer
"""
self.layernum = n
self.update()
[docs] def clear(self):
"""
Clear this slicer and its markers
"""
self.clear_markers()
self.inner_marker.remove()
self.top_line.remove()
self.bottom_line.remove()
[docs] def update(self, x=None, y=None):
"""
Draw the new roughness on the graph.
:param x: x-coordinates to reset current class x
:param y: y-coordinates to reset current class y
"""
# Reset x, y- coordinates if send as parameters
if x is not None:
self.x = numpy.sign(self.x) * numpy.fabs(x)
if y is not None:
self.y = numpy.sign(self.y) * numpy.fabs(y)
# Draw lines and markers
self.inner_marker.set(xdata=[0], ydata=[self.y])
self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
self.bottom_line.set(xdata=[self.x, -self.x], ydata=[-self.y, -self.y])
[docs] def save(self, ev):
"""
Remember the roughness for this layer and the next so that we
can restore on Esc.
"""
self.save_x = self.x
self.save_y = self.y
[docs] def moveend(self, ev):
"""
Called after a dragging this edge and set self.has_move to False
to specify the end of dragging motion
"""
self.has_move = False
self.base.moveend(ev)
[docs] def restore(self):
"""
Restore the roughness for this layer.
"""
self.x = self.save_x
self.y = self.save_y
[docs] def move(self, x, y, ev):
"""
Process move to a new position, making sure that the move is allowed.
"""
self.y = y
self.has_move = True
self.base.base.update()
[docs]class VerticalLines(BaseInteractor):
"""
Select an annulus through a 2D plot
"""
[docs] def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
"""
"""
BaseInteractor.__init__(self, base, axes, color=color)
self.markers = []
self.axes = axes
self.x = numpy.fabs(x)
self.save_x = self.x
self.y = numpy.fabs(y)
self.save_y = y
# Inner circle marker
self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
marker='s', markersize=10,
color=self.color, alpha=0.6,
pickradius=5, label="pick",
zorder=zorder, visible=True)[0]
self.right_line = self.axes.plot([self.x, self.x],
[self.y, -self.y],
linestyle='-', marker='',
color=self.color, visible=True)[0]
self.left_line = self.axes.plot([-self.x, -self.x],
[self.y, -self.y],
linestyle='-', marker='',
color=self.color, visible=True)[0]
self.has_move = False
self.connect_markers([self.right_line, self.inner_marker])
self.update()
[docs] def validate(self, param_name, param_value):
"""
Validate input from user
"""
return True
[docs] def set_layer(self, n):
"""
Allow adding plot to the same panel
:param n: the number of layer
"""
self.layernum = n
self.update()
[docs] def clear(self):
"""
Clear this slicer and its markers
"""
self.clear_markers()
self.inner_marker.remove()
self.left_line.remove()
self.right_line.remove()
[docs] def update(self, x=None, y=None):
"""
Draw the new roughness on the graph.
:param x: x-coordinates to reset current class x
:param y: y-coordinates to reset current class y
"""
# Reset x, y -coordinates if given as parameters
if x is not None:
self.x = numpy.sign(self.x) * numpy.fabs(x)
if y is not None:
self.y = numpy.sign(self.y) * numpy.fabs(y)
# Draw lines and markers
self.inner_marker.set(xdata=[self.x], ydata=[0])
self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y])
self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y])
[docs] def save(self, ev):
"""
Remember the roughness for this layer and the next so that we
can restore on Esc.
"""
self.save_x = self.x
self.save_y = self.y
[docs] def moveend(self, ev):
"""
Called after a dragging this edge and set self.has_move to False
to specify the end of dragging motion
"""
self.has_move = False
self.base.moveend(ev)
[docs] def restore(self):
"""
Restore the roughness for this layer.
"""
self.x = self.save_x
self.y = self.save_y
[docs] def move(self, x, y, ev):
"""
Process move to a new position, making sure that the move is allowed.
"""
self.has_move = True
self.x = x
self.base.base.update()
[docs]class BoxInteractorX(BoxInteractor):
"""
Average in Qx direction
"""
[docs] def __init__(self, base, axes, item=None, color='black', zorder=3):
BoxInteractor.__init__(self, base, axes, item=item, color=color)
self.base = base
self._post_data()
[docs] def _post_data(self):
"""
Post data creating by averaging in Qx direction
"""
from sas.sascalc.dataloader.manipulations import SlabX
self.post_data(SlabX, direction="X")
[docs] def validate(self, param_name, param_value):
"""
Validate input from user.
Values get checked at apply time.
"""
isValid = True
if param_name == 'nbins':
# Can't be 0
if param_value < 1:
print("Number of bins cannot be less than or equal to 0. Please adjust.")
isValid = False
return isValid
[docs]class BoxInteractorY(BoxInteractor):
"""
Average in Qy direction
"""
[docs] def __init__(self, base, axes, item=None, color='black', zorder=3):
BoxInteractor.__init__(self, base, axes, item=item, color=color)
self.base = base
self._post_data()
[docs] def _post_data(self):
"""
Post data creating by averaging in Qy direction
"""
from sas.sascalc.dataloader.manipulations import SlabY
self.post_data(SlabY, direction="Y")
[docs] def validate(self, param_name, param_value):
"""
Validate input from user
Values get checked at apply time.
"""
isValid = True
if param_name == 'nbins':
# Can't be 0
if param_value < 1:
print("Number of bins cannot be less than or equal to 0. Please adjust.")
isValid = False
return isValid