def calcProjectionVectors(R1,R2,norm=None): r1 = R1[:3] r2 = R2[:3] if not norm is None: NV = norm else: NV = np.cross(r1,r2) NV/= np.linalg.norm(NV) Zeros = np.isclose(NV,0.0) if np.sum(Zeros)==3: raise AttributeError('The two plane vectors are equivalen, {}, {}!'.format(r1,r2)) if np.sum(Zeros) == 2 or np.sum(Zeros)==1: # Easy case where the two vectors are to be along the x, y, or z directions if Zeros[0] == True: V1 = np.array([1.0,0.0,0.0]) V2 = np.cross(NV,V1) elif Zeros[1]: V1 = np.array([0.0,1.0,0.0]) V2 = np.cross(NV,V1) else: V1 = np.array([0.0,0.0,1.0]) V2 = np.cross(NV,V1) else: # The tricky case of all vectors having non-zero components. V1 = r1 V2 = r2 V1 = _tools.LengthOrder(V1) V2 = _tools.LengthOrder(V2) #for V in [V1,V2]: # Flip sign if needed # maxArg = np.argmax(np.abs(V)) # V*=np.sign(V[maxArg]) return V1,V2
def createQEAxes(DataSet=None,axis=0,figure = None, projectionVector1 = None, projectionVector2 = None): """Function to create Q E plot Kwargs: - DataSet (DataSet): If provided and no projections vectors creates QE axis for main direction (default None) - axis (int): Whether to create axis 0 or 1 (projection vector 0 or orthogonal to this, default 0) - figure (figure): If provided, this is used to create the axis within (default None) - projectionVector1 (vec): Projection vector along which data is plotted. If not provided sample vector is used (default None) - projectionVector2 (vec): Projection vector orthogonal to data. If not provided sample vector is used (default None) """ if projectionVector1 is None or projectionVector2 is None: v1 = DataSet.sample[0].projectionVector1 v2 = DataSet.sample[0].projectionVector2 angle = DataSet.sample[0].projectionAngle orientationMatrix = DataSet.sample[0].orientationMatrix else: v1 = np.array(projectionVector1) v2 = np.array(projectionVector2) if not np.all([x.shape==(3,) for x in [v1,v2]]) or not np.all([len(x.shape)==1 for x in [v1,v2]]): raise AttributeError('Provided vector(s) is not 3D: projectionVector1.shape={} or projectionVector2.shape={}'.format(v1.shape,v2.shape)) angle = np.arccos(np.dot(v1,v2)/(np.linalg.norm(v1)*np.linalg.norm(v2))) orientationMatrix = np.ones(3) sample = copy.deepcopy(DataSet.sample) v1,v2 = sample[0].projectionVector1,sample[0].projectionVector2 angle = np.sign(np.dot(np.cross(v1,v2),sample[0].planeNormal))*sample[0].projectionAngle v2Length = np.linalg.norm(v2)/np.linalg.norm(v1) projectionMatrix = np.linalg.inv(np.array([[1,0],[np.cos(angle)*v2Length,np.sin(angle)*v2Length]]).T) projectionVectorQX = np.dot(np.dot(projectionMatrix,[1,0]),np.array([v1,v2])) projectionVectorQY = np.dot(np.dot(projectionMatrix,[0,1]),np.array([v1,v2])) projectionVectorQX = _tools.LengthOrder(projectionVectorQX) projectionVectorQY = _tools.LengthOrder(projectionVectorQY) projectionVectorQXLength = np.linalg.norm(np.dot(orientationMatrix,projectionVectorQY)) projectionVectorQYLength = np.linalg.norm(np.dot(orientationMatrix,projectionVectorQX)) projectionVectorQXFormated = ', '.join(['{:.3f}'.format(x) for x in projectionVectorQX]) projectionVectorQYFormated = ', '.join(['{:.3f}'.format(x) for x in projectionVectorQY]) if axis == 0: projectionVectorLength = projectionVectorQYLength projectionVectorLengthORthogonal = projectionVectorQXLength projectionVectorFormated = projectionVectorQXFormated projectionVector = projectionVectorQX projectionVectorOrthogonal = projectionVectorQY elif axis == 1: projectionVectorLength = projectionVectorQXLength projectionVectorFormated = projectionVectorQYFormated projectionVectorLengthORthogonal = projectionVectorQYLength projectionVector = projectionVectorQY projectionVectorOrthogonal = projectionVectorQX else: raise AttributeError('Provided axis of {} is not allowed. Should be either 0 or 1.'.format(axis)) if figure is None: figure = plt.figure(figsize=(7, 4)) else: figure.clf() def inv_tr(l,x,y): return x*l,y def tr(l,x,y): return x/l,y if pythonVersion == 3: grid_locator1 = MultipleLocator(base=1.0) # Standard X ticks is multiple locator grid_helper = GridHelperCurveLinear((lambda x,y:inv_tr(projectionVectorLength,x,y), lambda x,y:tr(projectionVectorLength,x,y)),grid_locator1=grid_locator1) else: grid_helper = GridHelperCurveLinear((lambda x,y:inv_tr(projectionVectorLength,x,y), lambda x,y:tr(projectionVectorLength,x,y))) ax = SubplotHost(figure, 1, 1, 1, grid_helper=grid_helper) ax.sample = sample[0] figure.add_subplot(ax) #ax.set_aspect(1.) ax.grid(True, zorder=0) def calculateRLU(l,v1,x,y,v,step): return np.asarray(x)/l*v1+v*step, np.asarray(y) def format_coord(x,y): # pragma: no cover # x is H,K,L and y is energy xformated = ', '.join(['{} = {}'.format(Y[0],Y[1]) for Y in zip(['h','k','l'],['{:.4f}'.format(X) for X in x])]) return '{}, E={:.4f}'.format(xformated,y) ax.set_xlabel('{} [RLU]'.format(projectionVectorFormated)) ax.set_ylabel('E [meV]') ax._length = projectionVectorLengthORthogonal ax._projectionVector = projectionVector ax._projectionVectorOrthogonal = projectionVectorOrthogonal ax._step = 0.0 ax.calculateRLU = lambda x,y: calculateRLU(projectionVectorLength,ax._projectionVector,x,y,ax._projectionVectorOrthogonal,ax._step) ax.format_coord = lambda x,y: format_coord(*ax.calculateRLU(x,y)) if pythonVersion == 3: ax.forceGridUpdate = lambda:forceGridUpdate(ax) ax.xticks = 7 def xAxisChanged(axis, forceUpdate=False): locator = axis._grid_helper.grid_finder.grid_locator1 xlim = axis.get_xlim() xlimDiff = np.diff(xlim) if isinstance(locator,MultipleLocator): if hasattr(axis,'xBase'): base = axis.xBase else: base = calculateBase(locator,xlimDiff,axis.xticks) locator.set_params(base=base) elif isinstance(locator,MaxNLocator): if hasattr(axis,'xTicks'): ticks = getattr(axis,'xTicks') else: ticks = 7 locator.set_params(nbins = ticks) else: return axis.forceGridUpdate() ax.callbacks.connect('xlim_changed', xAxisChanged) ax.callbacks.connect('draw_event',lambda ax: xAxisChanged(ax,forceUpdate=True)) ax.xAxisChanged = lambda: xAxisChanged(ax,forceUpdate=True) @updateXAxisDecorator(ax=ax) def set_xticks_base(xBase=None,ax=ax): """Setter of the base x ticks to be used for plotting Kwargs: - xBase (float): Base of the tick marks (default automatic) """ if not isinstance(ax._grid_helper.grid_finder.grid_locator1,MultipleLocator): l1 = MultipleLocator(base=xBase) ax._grid_helper.update_grid_finder(grid_locator1=l1) if xBase is None: if hasattr(ax,'xBase'): delattr(ax,'xBase') else: ax.xBase = xBase @updateXAxisDecorator(ax=ax) def set_xticks_number(xNumber = None,ax=ax): """Setter of the number of x ticks to be used for plotting Kwargs: - xNumber (int): Number of x tick marks (default 7) """ if xNumber is None: xNumber = 7 if not isinstance(ax._grid_helper.grid_finder.grid_locator1,MaxNLocator): l1 = MaxNLocator(nbins=xNumber) ax._grid_helper.update_grid_finder(grid_locator1=l1) ax.xTicks = xNumber ax.set_xticks_base = set_xticks_base ax.set_xticks_number = set_xticks_number return ax
def __init__(self, data, bins, sample, log=False, *args, **kwargs): annoyingFigure = plt.gca().get_figure() if not len(annoyingFigure.get_children() ) <= 1: # If less than or equal to 1 child figure is empty plt.close( annoyingFigure ) # Mega hack to save the day (close annoying mpl figure....... not a fan). MDT = magic don't touch pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') super().__init__(*args, **kwargs) self.l = QtGui.QGridLayout() self.setLayout(self.l) # Add plotItem to allow for axes self.imv1 = CustomImageView(view=pg.PlotItem()) self.imv2 = pg.ImageView(view=pg.PlotItem()) #Insert widgets into layout self.l.addWidget(self.imv1, 0, 0) self.l.addWidget(self.imv2, 1, 0) # Create the region-of-interest line segment self.roi = pg.ROI([1, 0], [1, 1], pen='r', resizable=True) self.roi.setSize([1, 0.1]) self.roi.addScaleHandle( [0.5, 1], [0.5, 0.5], ) self.roi.addScaleHandle([0, 0.5], [0.5, 0.5]) self.roi.addRotateHandle([1.0, 1.0], [0.5, 0.5]) # Change color of roi markers handleColor = QtGui.QColor(255, 102, 0) for handle in self.roi.getHandles(): handle.pen.setColor(handleColor) self.imv1.addItem(self.roi) self.Data, self.bins = data, bins if len(data) == 4: with warnings.catch_warnings(): warnings.simplefilter("ignore") self.data = np.divide(self.Data[0] * self.Data[-1], self.Data[1] * self.Data[2]) self.data[self.Data[-1] == 0] = -np.nanmin( self.data[self.data != 0]) else: self.data = self.Data # set unmeasured areas to negative value if log: self.data = np.log10(self.data + 1e-20) # transpose data to comply with viewer requirements self.data = self.data.transpose((2, 0, 1)) # Extract the sample self.sample = sample # Calculate the projection directions self.axes = np.array( [self.sample.calculateQxQyToHKL(*X) for X in self.sample.RotMat]) ## Display the data self.imv1.setImage(self.data, xvals=self.bins[2][0, 0, :], levels=(-1e-7, 1e-5), autoLevels=False) self.imv1.setHistogramRange(-8.5e-8, 1e-5) # unlock aspect ratio self.imv1.view.setAspectLocked(False) # Generate and add correct labels to axes self.xaxis = self.imv1.view.axes['bottom']['item'] self.yaxis = self.imv1.view.axes['left']['item'] self.xlabel = _tools.generateLabel(_tools.LengthOrder(self.axes[0])) self.ylabel = _tools.generateLabel(_tools.LengthOrder(self.axes[1])) self.xaxis.setLabel(text=self.xlabel, units='RLU') self.yaxis.setLabel(text=self.ylabel, units='RLU') # Setup color map for main window self.setupColorscale(self.imv1) # Calcualte scaling and offsets between image view and RLU axes self.qxRange = np.diff(self.bins[0][[0, -1], 0, 0]) self.qyRange = np.diff(self.bins[1][0, [0, -1], 0]) self.ERange = np.diff(self.bins[2][0, 0, [0, -1]]) self.qxScale = self.qxRange * np.linalg.norm( self.axes[0]) / self.bins[0].shape[0] self.qyScale = self.qyRange * np.linalg.norm( self.axes[1]) / self.bins[0].shape[1] self.EScale = self.ERange / self.bins[0].shape[2] self.qxCenter = self.bins[0][0, 0, 0] * np.linalg.norm(self.axes[0]) self.qyCenter = self.bins[1][0, 0, 0] * np.linalg.norm(self.axes[1]) self.ECenter = self.bins[2][0, 0, 0] # Apply scaling and translation img1Item = self.imv1.getImageItem() img1Item.scale(self.qxScale, self.qyScale) img1Item.translate(self.qxCenter / self.qxScale, self.qyCenter / self.qyScale) # Un-invert yaxis self.imv1.view.getViewBox().invertY(False) self.imv1.view.autoRange(True) self.imv1.view.setAutoVisible(x=True, y=True) # Add labels colormap for cut window self.imv2.view.setLabel("left", "Energy", units='meV') self.imv2.view.setLabel("bottom", "HKL", units='RLU') self.setupColorscale(self.imv2) # Hide all unneeded menus and uninvert yaxes self.imv2.ui.roiBtn.hide() self.imv2.ui.menuBtn.hide() self.imv2.view.getViewBox().invertY(False) # Extract projection matrix used for position calculation along cut self.projectionMatrix = self.axes self.projectionMatrix[0] *= 1 / np.linalg.norm( self.projectionMatrix[0]) self.projectionMatrix[1] *= 1 / np.linalg.norm( self.projectionMatrix[1]) self.xaxis2 = self.imv2.view.axes['bottom']['item'] self.yaxis2 = self.imv2.view.axes['left']['item'] self.xaxis2.tickStrings = lambda values, scale, spacing: self.XtickStrings( self.roi, values, scale, spacing) self.yaxis2.tickStrings = lambda values, scale, spacing: self.YtickStrings( values, scale, spacing) # Create function to be called when cut changes # Connect update-function to correct slot self.roi.sigRegionChanged.connect(self.update) # Call update for initial position self.update()