def do_reduction(calibration): # load data data = ms.Load("HRP39180.RAW") # copy parameters from calibration to data ms.CopyInstrumentParameters(calibration, data) # Now move component on data workspace using a relative move, where that component was a detector in the calibrated workspace ms.MoveInstrumentComponent(data, DetectorID=1100,X=0.0,Y=0.0,Z=5.0,RelativePosition=True) return data.getDetector(0).getPos()
def adjustComponent(self, component, pos, rot): wks_name = self.wks_name if pos: msa.MoveInstrumentComponent(wks_name, component, X=pos[0], Y=pos[1], Z=pos[2], RelativePosition=False) if rot: (rotw, rotx, roty, rotz) = eulerToAngleAxis(rot[0], rot[1], rot[2], self.eulerConvention) msa.RotateInstrumentComponent( wks_name, component, X=rotx, Y=roty, Z=rotz, Angle=rotw, RelativeRotation=False) return
def _minimisation_func(self, x_0, wks_name, component, firstIndex, lastIndex, difc, mask): """ Basic minimization function used. Returns the chisquared difference between the expected difc and the new difc after the component has been moved or rotated. """ xmap = self._mapOptions(x_0) if self._move: api.MoveInstrumentComponent(wks_name, component, X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False) if self._rotate: (rotw, rotx, roty, rotz) = self._eulerToAngleAxis(xmap[3], xmap[4], xmap[5], self._eulerConvention) # YZX api.RotateInstrumentComponent(wks_name, component, X=rotx, Y=roty, Z=rotz, Angle=rotw, RelativeRotation=False) api.CalculateDIFC(InputWorkspace=wks_name, OutputWorkspace=wks_name) difc_new = api.mtd[wks_name].extractY().flatten( )[firstIndex:lastIndex + 1] if self._masking: difc_new = np.ma.masked_array(difc_new, mask) return chisquare(f_obs=difc, f_exp=difc_new)[0]
def PyExec(self): self._eulerConvention = self.getProperty('EulerConvention').value calWS = self.getProperty('CalibrationTable').value calWS = api.SortTableWorkspace(calWS, Columns='detid') maskWS = self.getProperty("MaskWorkspace").value difc = calWS.column('difc') if maskWS is not None: self._masking = True mask = maskWS.extractY().flatten() difc = np.ma.masked_array(difc, mask) detID = calWS.column('detid') if self.getProperty("Workspace").value is not None: wks_name = self.getProperty("Workspace").value.name() else: wks_name = "alignedWorkspace" api.LoadEmptyInstrument( Filename=self.getProperty("InstrumentFilename").value, OutputWorkspace=wks_name) # Make a dictionary of what options are being refined for sample/source. No rotation. for opt in self._optionsList[:3]: self._optionsDict[opt] = self.getProperty(opt).value for opt in self._optionsList[3:]: self._optionsDict[opt] = False # First fit L1 if selected for Source and/or Sample for component in "Source", "Sample": if self.getProperty("Fit" + component + "Position").value: self._move = True if component == "Sample": comp = api.mtd[wks_name].getInstrument().getSample() else: comp = api.mtd[wks_name].getInstrument().getSource() componentName = comp.getFullName() logger.notice("Working on " + componentName + " Starting position is " + str(comp.getPos())) firstIndex = 0 lastIndex = len(difc) if self._masking: mask_out = mask[firstIndex:lastIndex + 1] else: mask_out = None self._initialPos = [ comp.getPos().getX(), comp.getPos().getY(), comp.getPos().getZ(), 0, 0, 0 ] # Set up x0 and bounds lists x0List = [] boundsList = [] for iopt, opt in enumerate(self._optionsList[:3]): if self._optionsDict[opt]: x0List.append(self._initialPos[iopt]) boundsList.append( (self._initialPos[iopt] + self.getProperty("Min" + opt).value, self._initialPos[iopt] + self.getProperty("Max" + opt).value)) results = minimize(self._minimisation_func, x0=x0List, method='L-BFGS-B', args=(wks_name, componentName, firstIndex, lastIndex, difc[firstIndex:lastIndex + 1], mask_out), bounds=boundsList) # Apply the results to the output workspace xmap = self._mapOptions(results.x) # Need to grab the component again, as things have changed api.MoveInstrumentComponent(wks_name, componentName, X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False) comp = api.mtd[wks_name].getInstrument().getComponentByName( componentName) logger.notice("Finished " + componentName + " Final position is " + str(comp.getPos())) self._move = False # Now fit all the components if any components = self.getProperty("ComponentList").value # Make a dictionary of what options are being refined. for opt in self._optionsList: self._optionsDict[opt] = self.getProperty(opt).value self._move = (self._optionsDict["Xposition"] or self._optionsDict["Yposition"] or self._optionsDict["Zposition"]) self._rotate = (self._optionsDict["AlphaRotation"] or self._optionsDict["BetaRotation"] or self._optionsDict["GammaRotation"]) prog = Progress(self, start=0, end=1, nreports=len(components)) for component in components: comp = api.mtd[wks_name].getInstrument().getComponentByName( component) firstDetID = self._getFirstDetID(comp) firstIndex = detID.index(firstDetID) lastDetID = self._getLastDetID(comp) lastIndex = detID.index(lastDetID) if lastDetID - firstDetID != lastIndex - firstIndex: raise RuntimeError( "Calibration detid doesn't match instrument") eulerAngles = comp.getRotation().getEulerAngles( self._eulerConvention) logger.notice("Working on " + comp.getFullName() + " Starting position is " + str(comp.getPos()) + " Starting rotation is " + str(eulerAngles)) x0List = [] self._initialPos = [ comp.getPos().getX(), comp.getPos().getY(), comp.getPos().getZ(), eulerAngles[0], eulerAngles[1], eulerAngles[2] ] boundsList = [] if self._masking: mask_out = mask[firstIndex:lastIndex + 1] if mask_out.sum() == mask_out.size: self.log().warning( "All pixels in '%s' are masked. Skipping calibration." % component) continue else: mask_out = None for iopt, opt in enumerate(self._optionsList): if self._optionsDict[opt]: x0List.append(self._initialPos[iopt]) boundsList.append((self._initialPos[iopt] + self.getProperty("Min" + opt).value, self._initialPos[iopt] + self.getProperty("Max" + opt).value)) results = minimize(self._minimisation_func, x0=x0List, method='L-BFGS-B', args=(wks_name, component, firstIndex, lastIndex, difc[firstIndex:lastIndex + 1], mask_out), bounds=boundsList) # Apply the results to the output workspace xmap = self._mapOptions(results.x) if self._move: api.MoveInstrumentComponent(wks_name, component, X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False) if self._rotate: (rotw, rotx, roty, rotz) = self._eulerToAngleAxis(xmap[3], xmap[4], xmap[5], self._eulerConvention) api.RotateInstrumentComponent(wks_name, component, X=rotx, Y=roty, Z=rotz, Angle=rotw, RelativeRotation=False) # Need to grab the component again, as things have changed comp = api.mtd[wks_name].getInstrument().getComponentByName( component) logger.notice( "Finshed " + comp.getFullName() + " Final position is " + str(comp.getPos()) + " Final rotation is " + str(comp.getRotation().getEulerAngles(self._eulerConvention))) prog.report() logger.notice("Results applied to workspace " + wks_name)
def _minimisation_func(self, x_0, wks_name, component, firstIndex, lastIndex): """ Basic minimization function used. Returns the sum of the absolute values for the fractional peak deviations: .. math:: \\sum_i^{N_d}\\sum_j^{N_p} (1 - m_{i,j}) \\frac{|d_{i,j} - d_j^*|}{d_j^*} where :math:`N_d` is the number of detectors in the bank, :math:`N_p` is the number of reference peaks, and :math:`m_{i,j}` is the mask for peak :math:`j` and detector :math:`i`. The mask evaluates to 1 if the detector is defective or the peak is missing in the detector, otherwise the mask evaluates to zero. There's an implicit one-to-correspondence between array index of ``difc`` and workspace index of ``wks_name``, that is, between row index of the input TOFS table and workspace index of ``wks_name``. @param x_0 :: list of length 3 (new XYZ coordinates of the component) or length 6 (XYZ and rotation coords) @param wks_name :: name of a workspace with an embedded instrument. The instrument will be adjusted according to the new coordinates ``x_0`` for instrument component ``component``. It's pixel spectra will contain the new DIFC @param component :: name of the instrument component to be optimized @param firstIndex :: workspace index of first index of ``difc`` array to be considered when comparing old and new DIFC values. When fitting the source or sample, this is the first spectrum index. @param lastIndex :: workspace index of last index of ``difc`` array to be considered when comparing old and new DIFC values. When fitting the source or sample, this is the last row number of the input TOFS table. @return Chi-square value between old and new DIFC values for the unmasked spectra """ xmap = self._mapOptions( x_0) # pad null rotations when x_0 contains only translations if self._move: api.MoveInstrumentComponent(wks_name, component, X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False, EnableLogging=False) if self._rotate: (rotw, rotx, roty, rotz) = self._eulerToAngleAxis(xmap[3], xmap[4], xmap[5], self._eulerConvention) # YZX api.RotateInstrumentComponent(wks_name, component, X=rotx, Y=roty, Z=rotz, Angle=rotw, RelativeRotation=False, EnableLogging=False) api.CalculateDIFC(InputWorkspace=wks_name, OutputWorkspace=wks_name, EnableLogging=False) difc = api.mtd[wks_name].extractY().flatten()[firstIndex:lastIndex + 1] peaks_d = self.peaks_tof[ firstIndex:lastIndex + 1] / difc[:, np.newaxis] # peak centers in d-spacing units # calculate the fractional peak center deviations, then sum their absolute values return np.sum(np.abs((peaks_d - self.peaks_ref) / self.peaks_ref))
def PyExec(self): table_tof = self.getProperty('PeakCentersTofTable').value self.peaks_tof = self._extract_tofs(table_tof) detector_count, peak_count = self.peaks_tof.shape table_tof = api.SortTableWorkspace(table_tof, Columns='detid') detID = table_tof.column('detid') peaks_ref = np.sort(self.getProperty( 'PeakPositions').value) # sort by increasing value self.peaks_ref = peaks_ref[np.newaxis, :] # shape = (1, peak_count) # Process input mask maskWS = self.getProperty("MaskWorkspace").value if maskWS is not None: mask = maskWS.extractY().flatten() # shape=(detector_count,) peaks_mask = np.tile( mask[:, np.newaxis], peak_count) # shape=(detector_count, peak_count) else: peaks_mask = np.zeros( (detector_count, peak_count)) # no detectors are masked peaks_mask[np.isnan(self.peaks_tof)] = True # mask the defective detectors and missing peaks self.peaks_tof = np.ma.masked_array(self.peaks_tof, peaks_mask) input_workspace = self.getProperty('InputWorkspace').value # Table containing the optimized absolute locations and orientations for each component adjustments_table_name = self.getProperty('AdjustmentsTable').value if len(adjustments_table_name) > 0: adjustments_table = self._initialize_adjustments_table( adjustments_table_name) saving_adjustments = True else: saving_adjustments = False # Table containing the relative changes in position and euler angles for each bank component displacements_table_name = self.getProperty('DisplacementsTable').value if len(displacements_table_name) > 0: displacements_table = self._initialize_displacements_table( displacements_table_name) saving_displacements = True else: saving_displacements = False self._eulerConvention = self.getProperty('EulerConvention').value output_workspace = self.getPropertyValue("OutputWorkspace") wks_name = '__alignedworkspace' # workspace whose counts will be DIFC values if bool(input_workspace) is True: api.CloneWorkspace(InputWorkspace=input_workspace, OutputWorkspace=wks_name) if output_workspace != str(input_workspace): api.CloneWorkspace(InputWorkspace=input_workspace, OutputWorkspace=output_workspace) else: api.LoadEmptyInstrument( Filename=self.getProperty("InstrumentFilename").value, OutputWorkspace=wks_name) # Make a dictionary of what options are being refined for sample/source. No rotation. for translation_option in self._optionsList[:3]: self._optionsDict[translation_option] = self.getProperty( translation_option).value for rotation_option in self._optionsList[3:]: self._optionsDict[rotation_option] = False # First fit L1 if selected for Source and/or Sample sample_position_begin = api.mtd[wks_name].getInstrument().getSample( ).getPos() for component in "Source", "Sample": # fit first the source position, then the sample position if self.getProperty("Fit" + component + "Position").value: self._move = True if component == "Sample": comp = api.mtd[wks_name].getInstrument().getSample() else: comp = api.mtd[wks_name].getInstrument().getSource() componentName = comp.getFullName() logger.notice("Working on " + componentName + " Starting position is " + str(comp.getPos())) firstIndex = 0 lastIndex = detector_count - 1 self._initialPos = [ comp.getPos().getX(), comp.getPos().getY(), comp.getPos().getZ(), 0, 0, 0 ] # no rotation # Set up x0 and bounds lists x0List = [] # initial X, Y, Z coordinates boundsList = [] # [(minX, maxX), (minZ, maxZ), (minZ, maxZ)] for iopt, translation_option in enumerate( self._optionsList[:3]): # iterate over X, Y, and Z if self._optionsDict[translation_option]: x0List.append(self._initialPos[iopt]) # default range for X is (x0 - 0.1m, x0 + 0.1m), same for Y and Z boundsList.append(( self._initialPos[iopt] + self.getProperty("Min" + translation_option).value, self._initialPos[iopt] + self.getProperty("Max" + translation_option).value)) # scipy.opimize.minimize with the L-BFGS-B algorithm results: OptimizeResult = minimize( self._minimisation_func, x0=x0List, method='L-BFGS-B', args=(wks_name, componentName, firstIndex, lastIndex), bounds=boundsList) # Apply the results to the output workspace xmap = self._mapOptions(results.x) # Save translation and rotations, if requested if saving_adjustments: instrument = api.mtd[wks_name].getInstrument() name_finder = { 'Source': instrument.getSource().getName(), 'Sample': instrument.getSample().getName() } component_adjustments = [ name_finder[component] ] + xmap[:3] + [0.0] * 4 # no rotations adjustments_table.addRow(component_adjustments) # Need to grab the component again, as things have changed kwargs = dict(X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False, EnableLogging=False) api.MoveInstrumentComponent(wks_name, componentName, **kwargs) # adjust workspace api.MoveInstrumentComponent(output_workspace, componentName, **kwargs) # adjust workspace comp = api.mtd[wks_name].getInstrument().getComponentByName( componentName) logger.notice("Finished " + componentName + " Final position is " + str(comp.getPos())) self._move = False sample_position_end = api.mtd[wks_name].getInstrument().getSample( ).getPos() # Now fit all the remaining components, if any components = self.getProperty("ComponentList").value # Make a dictionary of what translational and rotational options are being refined. for opt in self._optionsList: self._optionsDict[opt] = self.getProperty(opt).value self._move = any([ self._optionsDict[t] for t in ('Xposition', 'Yposition', 'Zposition') ]) self._rotate = any([ self._optionsDict[r] for r in ('AlphaRotation', 'BetaRotation', 'GammaRotation') ]) prog = Progress(self, start=0, end=1, nreports=len(components)) for component in components: comp = api.mtd[wks_name].getInstrument().getComponentByName( component) firstDetID = self._getFirstDetID(comp) firstIndex = detID.index( firstDetID) # a row index in the input TOFS table lastDetID = self._getLastDetID(comp) lastIndex = detID.index( lastDetID) # a row index in the input TOFS table if lastDetID - firstDetID != lastIndex - firstIndex: raise RuntimeError("TOFS detid doesn't match instrument") eulerAngles: List[float] = comp.getRotation().getEulerAngles( self._eulerConvention) logger.notice("Working on " + comp.getFullName() + " Starting position is " + str(comp.getPos()) + " Starting rotation is " + str(eulerAngles)) x0List = [] self._initialPos = [ comp.getPos().getX(), comp.getPos().getY(), comp.getPos().getZ(), eulerAngles[0], eulerAngles[1], eulerAngles[2] ] # Distance between the original position of the sample and the original position of the component comp_sample_distance_begin = (comp.getPos() - sample_position_begin).norm() boundsList = [] if np.all(peaks_mask[firstIndex:lastIndex + 1].astype(bool)): self.log().warning( "All pixels in '%s' are masked. Skipping calibration." % component) continue for iopt, opt in enumerate(self._optionsList): if self._optionsDict[opt]: x0List.append(self._initialPos[iopt]) boundsList.append((self._initialPos[iopt] + self.getProperty("Min" + opt).value, self._initialPos[iopt] + self.getProperty("Max" + opt).value)) minimizer_selection = self.getProperty('Minimizer').value if minimizer_selection == 'L-BFGS-B': # scipy.opimize.minimize with the L-BFGS-B algorithm results: OptimizeResult = minimize(self._minimisation_func, x0=x0List, method='L-BFGS-B', args=(wks_name, component, firstIndex, lastIndex), bounds=boundsList) elif minimizer_selection == 'differential_evolution': results: OptimizeResult = differential_evolution( self._minimisation_func, bounds=boundsList, args=(wks_name, component, firstIndex, lastIndex), maxiter=self.getProperty('MaxIterations').value) # Apply the results to the output workspace xmap = self._mapOptions(results.x) comp = api.mtd[wks_name].getInstrument().getComponentByName( component) # adjusted component # Distance between the adjusted position of the sample and the adjusted position of the component comp_sample_distance_end = (comp.getPos() - sample_position_end).norm() component_adjustments = [ 0. ] * 7 # 3 for translation, 3 for rotation axis, 1 for rotation angle component_displacements = [ 0. ] * 7 # 1 for distnace, 3 for translation, 3 for Euler angles component_displacements[0] = 1000 * ( comp_sample_distance_end - comp_sample_distance_begin ) # in mili-meters if self._move: kwargs = dict(X=xmap[0], Y=xmap[1], Z=xmap[2], RelativePosition=False, EnableLogging=False) api.MoveInstrumentComponent(wks_name, component, **kwargs) # adjust workspace api.MoveInstrumentComponent(output_workspace, component, **kwargs) # adjust workspace component_adjustments[:3] = xmap[:3] for i in range(3): component_displacements[i + 1] = 1000 * ( xmap[i] - self._initialPos[i]) # in mili-meters if self._rotate: (rotw, rotx, roty, rotz) = self._eulerToAngleAxis(xmap[3], xmap[4], xmap[5], self._eulerConvention) kwargs = dict(X=rotx, Y=roty, Z=rotz, Angle=rotw, RelativeRotation=False, EnableLogging=False) api.RotateInstrumentComponent(wks_name, component, **kwargs) # adjust workspace api.RotateInstrumentComponent(output_workspace, component, **kwargs) # adjust workspace component_adjustments[3:] = [rotx, roty, rotz, rotw] for i in range(3, 6): component_displacements[ i + 1] = xmap[i] - self._initialPos[i] # in degrees if saving_adjustments and (self._move or self._rotate): adjustments_table.addRow([component] + component_adjustments) if saving_displacements and (self._move or self._rotate): displacements_table.addRow([component] + component_displacements) # Need to grab the component object again, as things have changed logger.notice( "Finished " + comp.getFullName() + " Final position is " + str(comp.getPos()) + " Final rotation is " + str(comp.getRotation().getEulerAngles(self._eulerConvention))) prog.report() api.DeleteWorkspace(wks_name) self.setProperty("OutputWorkspace", output_workspace) logger.notice("Results applied to workspace " + wks_name)