Exemple #1
0
 def test_fix_yaw(self) -> None:
     CloneWorkspace(InputWorkspace='test_workspace_TOF',
                    OutputWorkspace='perturbed')
     RotateInstrumentComponent(Workspace='perturbed',
                               ComponentName='bank1',
                               X=0,
                               Y=0,
                               z=1,
                               Angle=5,
                               RelativeRotation=True)
     r"""Pass option FixYaw=True"""
     CorelliPowderCalibrationCreate(InputWorkspace='perturbed',
                                    OutputWorkspacesPrefix='cal_',
                                    TofBinning=[300, 1.0, 16666.7],
                                    PeakPositions=self.spacings_reference,
                                    SourceToSampleDistance=10.0,
                                    ComponentList='bank1',
                                    ComponentMaxTranslation=0.2,
                                    FixYaw=True,
                                    ComponentMaxRotation=10,
                                    Minimizer='L-BFGS-B')
     # Check no change in the rotations around Z-axis of first bank
     row = mtd['cal_displacements'].row(0)
     self.assertAlmostEquals(row['DeltaGamma'], 0.0, places=5)
     DeleteWorkspaces(['perturbed'])
    def _create_workspaces(self):
        cal=CreateSampleWorkspace(NumBanks=1,BinWidth=20000,PixelSpacing=0.1,BankPixelWidth=100)
        RotateInstrumentComponent(cal, ComponentName='bank1', X=1, Y=0.5, Z=2, Angle=35)
        MoveInstrumentComponent(cal, ComponentName='bank1', X=1, Y=1, Z=5)
        bkg=CloneWorkspace(cal)
        data=CloneWorkspace(cal)
        AddSampleLog(cal, LogName="gd_prtn_chrg", LogType='Number', NumberType='Double', LogText='200')
        AddSampleLog(bkg, LogName="gd_prtn_chrg", LogType='Number', NumberType='Double', LogText='50')
        AddSampleLog(data, LogName="gd_prtn_chrg", LogType='Number', NumberType='Double', LogText='100')
        AddSampleLog(cal, LogName="duration", LogType='Number', NumberType='Double', LogText='20')
        AddSampleLog(bkg, LogName="duration", LogType='Number', NumberType='Double', LogText='5')
        AddSampleLog(data, LogName="duration", LogType='Number', NumberType='Double', LogText='10')

        def get_cal_counts(n):
            if n < 5000:
                return 0.9
            else:
                return 1.0

        def get_bkg_counts(n):
            return 1.5*get_cal_counts(n)

        def get_data_counts(n,twoTheta):
            tt1=30
            tt2=45
            return get_bkg_counts(n)+10*np.exp(-(twoTheta-tt1)**2/1)+20*np.exp(-(twoTheta-tt2)**2/0.2)

        for i in range(cal.getNumberHistograms()):
            cal.setY(i, [get_cal_counts(i)*2.0])
            bkg.setY(i, [get_bkg_counts(i)/2.0])
            twoTheta=data.getInstrument().getDetector(i+10000).getTwoTheta(V3D(0,0,0),V3D(0,0,1))*180/np.pi
            data.setY(i, [get_data_counts(i,twoTheta)])

        return data, cal, bkg
 def _correctForFractionalForegroundCentre(self, ws, summedForeground):
     """
     This needs to be called after having summed the foreground but before transfering to momentum transfer.
     This needs to be called in both coherent and incoherent cases, regardless the angle calibration option.
     The reason for this is that up to this point is the fractional workspace index that correponds to the calibrated 2theta.
     However the momentum transfer calculation, which normally comes after summing the foreground,
     takes the 2theta from the spectrumInfo of the summed foreground workspace.
     Hence this code below translated the detector by the difference of the
     fractional and integer foreground centre along the detector plane.
     It also applies local rotation so that the detector continues to face the sample.
     Note that this translation has nothing to do with the difference of foreground centres in direct and reflected beams,
     which is handled already in pre-process algorithm.
     Here it's only about the difference of the fractional and integer foreground centre of the reflected beam
     with already calibrated angle no matter the option.
     Note also, that this could probably be avoided, if the loader placed
     the integer foreground at the given angle and not the fractional one.
     Fractional foreground centre only matter when calculating the difference between direct and reflected beams.
     But for the final Q (and sigma) calculation, it takes the position/angle from spectrumInfo()...(0),
     which corresponds to the centre of the pixel."""
     foreground = self._foregroundIndices(ws)
     # integer foreground centre
     beamPosIndex = foreground[1]
     # fractional foreground centre
     linePosition = ws.run().getProperty(
         common.SampleLogs.LINE_POSITION).value
     l2 = ws.run().getProperty('L2').value
     instr = common.instrumentName(ws)
     pixelSize = common.pixelSize(instr)
     # the distance between the fractional and integer foreground centres along the detector plane
     dist = pixelSize * (linePosition - beamPosIndex)
     if dist != 0.:
         detPoint1 = ws.spectrumInfo().position(0)
         detPoint2 = ws.spectrumInfo().position(20)
         beta = numpy.math.atan2((detPoint2[0] - detPoint1[0]),
                                 (detPoint2[2] - detPoint1[2]))
         xvsy = numpy.math.sin(beta) * dist
         mz = numpy.math.cos(beta) * dist
         if instr == 'D17':
             mx = xvsy
             my = 0.0
             rotationAxis = [0, 1, 0]
         else:
             mx = 0.0
             my = xvsy
             rotationAxis = [-1, 0, 0]
         MoveInstrumentComponent(Workspace=summedForeground,
                                 ComponentName='detector',
                                 X=mx,
                                 Y=my,
                                 Z=mz,
                                 RelativePosition=True)
         angle_corr = numpy.arctan2(dist, l2) * 180 / numpy.pi
         RotateInstrumentComponent(Workspace=summedForeground,
                                   ComponentName='detector',
                                   X=rotationAxis[0],
                                   Y=rotationAxis[1],
                                   Z=rotationAxis[2],
                                   Angle=angle_corr,
                                   RelativeRotation=True)
     return summedForeground
Exemple #4
0
 def setUp(self) -> None:
     r"""Fixture runs at the beginning of every test method"""
     spacings_reference = [
         0.9179, 0.9600, 1.0451, 1.2458, 1.3576, 1.5677, 1.6374, 3.1353
     ]  # silicon
     # add one Gaussian peak for every reference d-spacing
     peak_functions = list()
     for spacing in spacings_reference:
         peak_function = f'name=Gaussian, PeakCentre={spacing}, Height={10 * np.sqrt(spacing)}, Sigma={0.003 * spacing}'
         peak_functions.append(peak_function)
     function = ';'.join(peak_functions)
     begin, end, bin_width = spacings_reference[
         0] - 0.5, spacings_reference[-1] + 0.5, 0.0001
     # Single 10x10 rectangular detector, located 4m downstream the sample along the X-axis
     # Each detector has the same histogram of intensities, showing eight Gaussian peaks with centers at the
     # reference d-spacings
     CreateSampleWorkspace(WorkspaceType='Histogram',
                           Function='User Defined',
                           UserDefinedFunction=function,
                           XUnit='dSpacing',
                           XMin=begin,
                           XMax=end,
                           BinWidth=bin_width,
                           NumBanks=1,
                           PixelSpacing=0.02,
                           SourceDistanceFromSample=10.0,
                           BankDistanceFromSample=4.0,
                           OutputWorkspace='test_workspace_dSpacing')
     RotateInstrumentComponent(Workspace='test_workspace_dSpacing',
                               ComponentName='bank1',
                               X=0.,
                               Y=1.,
                               z=0.,
                               Angle=90,
                               RelativeRotation=True)
     MoveInstrumentComponent(Workspace='test_workspace_dSpacing',
                             ComponentName='bank1',
                             X=4.0,
                             y=0.0,
                             z=0.0,
                             RelativePosition=False)
     # Eight peaks now in TOF. Only when the instrument is located 4m downstream along the X-axis will we obtain
     # the correct d-Spacings if we convert back to dSpacings units. If we perturb the instrument and convert
     # back to dSpacing units, we'll obtain eight peaks centered at d-spacings sligthly different than the
     # reference values
     ConvertUnits(InputWorkspace='test_workspace_dSpacing',
                  Target='TOF',
                  EMode='Elastic',
                  OutputWorkspace='test_workspace_TOF')
     Rebin(InputWorkspace='test_workspace_TOF',
           Params=[300, 1.0, 16666.7],
           OutputWorkspace='test_workspace_TOF')
     ConvertUnits(InputWorkspace='test_workspace_TOF',
                  Target='dSpacing',
                  EMode='Elastic',
                  OutputWorkspace='test_workspace_dSpacing')
     self.spacings_reference = spacings_reference
Exemple #5
0
 def test_rotation(self):
     CloneWorkspace(InputWorkspace='test_workspace_TOF',
                    OutputWorkspace='perturbed')
     RotateInstrumentComponent(Workspace='perturbed',
                               ComponentName='bank1',
                               X=0,
                               Y=0,
                               z=1,
                               Angle=5,
                               RelativeRotation=True)
     assert self.spacings_recovered('perturbed', calibrate=False) is False
     assert self.spacings_recovered('perturbed', calibrate=True)
     DeleteWorkspaces(['perturbed'])
 def serve_instrument(output_workspace):
     scattering_angle = 20  # in degrees
     # Instrument with four spectra. Each spectrum has one single Gaussian peak in TOF, centered at 10000
     CreateSampleWorkspace(OutputWorkspace=output_workspace, BinWidth=0.1, NumBanks=1, BankPixelWidth=2,
                           Function='User Defined',
                           UserDefinedFunction='name=Gaussian, PeakCentre=10000, Height=100, Sigma=2',
                           Xmin=9900, Xmax=10100, BankDistanceFromSample=2, SourceDistanceFromSample=20)
     MoveInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', RelativePosition=False,
                             X=0, Y=0.0, Z=0)  # move to the origin
     RotateInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', X=0, Y=1, Z=0,
                               Angle=scattering_angle, RelativeRotation=False)
     sin, cos = np.sin(np.radians(scattering_angle)), np.cos(np.radians(scattering_angle))
     # detector pixel width is 0.008m, perpendicular to the scattered beam. Detector is 2m away from the sample
     z, x = 2 * cos + 0.004 * sin, 2 * sin - 0.004 * cos
     MoveInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', RelativePosition=False,
                             X=x, Y=0, Z=z)  # translate 2 meters away and center the detector
     return mtd['original']
Exemple #7
0
    def testAlignComponentsRotationY(self):
        CreateSampleWorkspace(OutputWorkspace='testWS',
                              NumBanks=1,
                              BankPixelWidth=4)
        component = 'bank1'
        MoveInstrumentComponent(Workspace='testWS',
                                ComponentName=component,
                                X=2.00,
                                Y=0,
                                Z=2.00,
                                RelativePosition=False)
        RotateInstrumentComponent(Workspace='testWS',
                                  ComponentName='bank1',
                                  X=0,
                                  Y=1,
                                  Z=0,
                                  Angle=50,
                                  RelativeRotation=False)

        ### Detector should rotate to +45deg around Y
        ### Calibration table generated with:
        # CreateSampleWorkspace(OutputWorkspace='sample2', NumBanks=1,BankPixelWidth=4)
        # MoveInstrumentComponent(Workspace='sample2',ComponentName='bank1',X=2.0,Y=0.0,Z=2.0,RelativePosition=False)
        # RotateInstrumentComponent(Workspace='sample2',ComponentName='bank1',X=0,Y=1,Z=0,Angle=45,RelativeRotation=False)
        # CalculateDIFC(InputWorkspace='sample2', OutputWorkspace='sample2')
        # d=mtd['sample2'].extractY()
        # for i in range(len(d)):
        #        print "calTable.addRow(["+str(i+16)+", "+str(d[i][0])+"])"

        calTable = CreateEmptyTableWorkspace()
        calTable.addColumn("int", "detid")
        calTable.addColumn("double", "difc")

        calTable.addRow([16, 2481.89300158])
        calTable.addRow([17, 2481.90717397])
        calTable.addRow([18, 2481.94969])
        calTable.addRow([19, 2482.02054626])
        calTable.addRow([20, 2490.36640334])
        calTable.addRow([21, 2490.38050851])
        calTable.addRow([22, 2490.42282292])
        calTable.addRow([23, 2490.49334316])
        calTable.addRow([24, 2498.83911141])
        calTable.addRow([25, 2498.85314962])
        calTable.addRow([26, 2498.89526313])
        calTable.addRow([27, 2498.96544859])
        calTable.addRow([28, 2507.31101837])
        calTable.addRow([29, 2507.32498986])
        calTable.addRow([30, 2507.36690322])
        calTable.addRow([31, 2507.43675513])

        ws = mtd["testWS"]
        startPos = ws.getInstrument().getComponentByName(component).getPos()
        startRot = ws.getInstrument().getComponentByName(
            component).getRotation().getEulerAngles("YZX")  #YZX
        AlignComponents(CalibrationTable="calTable",
                        Workspace="testWS",
                        ComponentList=component,
                        AlphaRotation=True)
        ws = mtd["testWS"]
        endPos = ws.getInstrument().getComponentByName(component).getPos()
        endRot = ws.getInstrument().getComponentByName(
            component).getRotation().getEulerAngles("YZX")  #YZX
        self.assertEqual(startPos, endPos)
        self.assertAlmostEqual(endRot[0], 45.0, places=0)
        self.assertEqual(startRot[1], endRot[1])
        self.assertEqual(startRot[2], endRot[2])
Exemple #8
0
    def PyExec(self):
        fn = self.getPropertyValue("Filename")
        wsn = self.getPropertyValue("OutputWorkspace")
        #print (fn, wsn)

        self.fxml = self.getPropertyValue("InstrumentXML")

        #load data

        parms_dict, det_udet, det_count, det_tbc, data = self.read_file(fn)
        nrows = int(parms_dict['NDET'])
        #nbins=int(parms_dict['NTC'])
        xdata = np.array(det_tbc)
        xdata_mon = np.linspace(xdata[0], xdata[-1], len(xdata))
        ydata = data.astype(np.float)
        ydata = ydata.reshape(nrows, -1)
        edata = np.sqrt(ydata)
        #CreateWorkspace(OutputWorkspace=wsn,DataX=xdata,DataY=ydata,DataE=edata,
        #                NSpec=nrows,UnitX='TOF',WorkspaceTitle='Data',YUnitLabel='Counts')
        nr, nc = ydata.shape
        ws = WorkspaceFactory.create("Workspace2D",
                                     NVectors=nr,
                                     XLength=nc + 1,
                                     YLength=nc)
        for i in range(nrows):
            ws.setX(i, xdata)
            ws.setY(i, ydata[i])
            ws.setE(i, edata[i])
        ws.getAxis(0).setUnit('tof')
        AnalysisDataService.addOrReplace(wsn, ws)

        #self.setProperty("OutputWorkspace", wsn)
        #print ("ws:", wsn)
        #ws=mtd[wsn]

        # fix the x values for the monitor
        for i in range(nrows - 2, nrows):
            ws.setX(i, xdata_mon)
        self.log().information("set detector IDs")
        #set detetector IDs
        for i in range(nrows):
            ws.getSpectrum(i).setDetectorID(det_udet[i])
        #Sample_logs the header values are written into the sample logs
        log_names = [sl.encode('ascii', 'ignore') for sl in parms_dict.keys()]
        log_values = [
            sl.encode('ascii', 'ignore')
            if isinstance(sl, types.UnicodeType) else str(sl)
            for sl in parms_dict.values()
        ]
        AddSampleLogMultiple(Workspace=wsn,
                             LogNames=log_names,
                             LogValues=log_values)
        SetGoniometer(Workspace=wsn, Goniometers='Universal')
        if (self.fxml == ""):
            LoadInstrument(Workspace=wsn,
                           InstrumentName="Exed",
                           RewriteSpectraMap=True)
        else:
            LoadInstrument(Workspace=wsn,
                           Filename=self.fxml,
                           RewriteSpectraMap=True)
        RotateInstrumentComponent(
            Workspace=wsn,
            ComponentName='Tank',
            Y=1,
            Angle=-float(parms_dict['phi'].encode('ascii', 'ignore')),
            RelativeRotation=False)
        # Separate monitors into seperate workspace
        ExtractSpectra(InputWorkspace=wsn,
                       WorkspaceIndexList=','.join(
                           [str(s) for s in range(nrows - 2, nrows)]),
                       OutputWorkspace=wsn + '_Monitors')
        MaskDetectors(Workspace=wsn,
                      WorkspaceIndexList=','.join(
                          [str(s) for s in range(nrows - 2, nrows)]))
        RemoveMaskedSpectra(InputWorkspace=wsn, OutputWorkspace=wsn)

        self.setProperty("OutputWorkspace", wsn)
Exemple #9
0
    def PyExec(self):
        fn = self.getPropertyValue("Filename")
        wsn = self.getPropertyValue("OutputWorkspace")
        monitor_workspace_name = self.getPropertyValue(
            "OutputMonitorWorkspace")
        if monitor_workspace_name == "":
            self.setPropertyValue("OutputMonitorWorkspace", wsn + '_Monitors')
        # print (fn, wsn)
        self.override_angle = self.getPropertyValue("AngleOverride")
        self.fxml = self.getPropertyValue("InstrumentXML")

        # load data

        parms_dict, det_udet, det_count, det_tbc, data = self.read_file(fn)
        nrows = int(parms_dict['NDET'])
        # nbins=int(parms_dict['NTC'])
        xdata = np.array(det_tbc)
        xdata_mon = np.linspace(xdata[0], xdata[-1], len(xdata))
        ydata = data.astype(float)
        ydata = ydata.reshape(nrows, -1)
        edata = np.sqrt(ydata)
        # CreateWorkspace(OutputWorkspace=wsn,DataX=xdata,DataY=ydata,DataE=edata,
        #                NSpec=nrows,UnitX='TOF',WorkspaceTitle='Data',YUnitLabel='Counts')
        nr, nc = ydata.shape
        ws = WorkspaceFactory.create("Workspace2D",
                                     NVectors=nr,
                                     XLength=nc + 1,
                                     YLength=nc)
        for i in range(nrows):
            ws.setX(i, xdata)
            ws.setY(i, ydata[i])
            ws.setE(i, edata[i])
        ws.getAxis(0).setUnit('tof')
        AnalysisDataService.addOrReplace(wsn, ws)

        # self.setProperty("OutputWorkspace", wsn)
        # print ("ws:", wsn)
        # ws=mtd[wsn]

        # fix the x values for the monitor
        for i in range(nrows - 2, nrows):
            ws.setX(i, xdata_mon)
        self.log().information("set detector IDs")
        # set detetector IDs
        for i in range(nrows):
            ws.getSpectrum(i).setDetectorID(det_udet[i])
        # Sample_logs the header values are written into the sample logs
        log_names = [
            str(sl.encode('ascii', 'ignore').decode())
            for sl in parms_dict.keys()
        ]
        log_values = [
            str(sl.encode('ascii', 'ignore').decode()) if isinstance(
                sl, UnicodeType) else str(sl) for sl in parms_dict.values()
        ]
        for i in range(len(log_values)):
            if ('nan' in log_values[i]) or ('NaN' in log_values[i]):
                log_values[i] = '-1.0'
        AddSampleLogMultiple(Workspace=wsn,
                             LogNames=log_names,
                             LogValues=log_values)
        SetGoniometer(Workspace=wsn, Goniometers='Universal')
        if (self.fxml == ""):
            LoadInstrument(Workspace=wsn,
                           InstrumentName="Exed",
                           RewriteSpectraMap=True)
        else:
            LoadInstrument(Workspace=wsn,
                           Filename=self.fxml,
                           RewriteSpectraMap=True)
        try:
            RotateInstrumentComponent(
                Workspace=wsn,
                ComponentName='Tank',
                Y=1,
                Angle=-float(parms_dict['phi'].encode('ascii', 'ignore')),
                RelativeRotation=False)
        except:
            self.log().warning(
                "The instrument does not contain a 'Tank' component. "
                "This means that you are using a custom XML instrument definition. "
                "OMEGA_MAG will be ignored.")
            self.log().warning(
                "Please make sure that the detector positions in the instrument definition are correct."
            )
        # Separate monitors into seperate workspace
        __temp_monitors = ExtractSpectra(
            InputWorkspace=wsn,
            WorkspaceIndexList=','.join(
                [str(s) for s in range(nrows - 2, nrows)]),
            OutputWorkspace=self.getPropertyValue("OutputMonitorWorkspace"))
        # ExtractSpectra(InputWorkspace = wsn, WorkspaceIndexList = ','.join([str(s) for s in range(nrows-2, nrows)]),
        # OutputWorkspace = wsn + '_Monitors')
        MaskDetectors(Workspace=wsn,
                      WorkspaceIndexList=','.join(
                          [str(s) for s in range(nrows - 2, nrows)]))
        RemoveMaskedSpectra(InputWorkspace=wsn, OutputWorkspace=wsn)

        self.setProperty("OutputWorkspace", wsn)
        self.setProperty("OutputMonitorWorkspace", __temp_monitors)
Exemple #10
0
    def _sumForegroundInLambda(self, ws):
        """Sum the foreground region into a single histogram."""
        foreground = self._foregroundIndices(ws)
        sumIndices = [i for i in range(foreground[0], foreground[2] + 1)]
        beamPosIndex = foreground[1]
        foregroundWSName = self._names.withSuffix('grouped')
        foregroundWS = ExtractSingleSpectrum(InputWorkspace=ws,
                                             OutputWorkspace=foregroundWSName,
                                             WorkspaceIndex=beamPosIndex,
                                             EnableLogging=self._subalgLogging)
        maxIndex = ws.getNumberHistograms() - 1
        foregroundYs = foregroundWS.dataY(0)
        foregroundEs = foregroundWS.dataE(0)
        numpy.square(foregroundEs, out=foregroundEs)
        for i in sumIndices:
            if i == beamPosIndex:
                continue
            if i < 0 or i > maxIndex:
                self.log().warning(
                    'Foreground partially out of the workspace.')
            addeeWSName = self._names.withSuffix('addee')
            addeeWS = ExtractSingleSpectrum(InputWorkspace=ws,
                                            OutputWorkspace=addeeWSName,
                                            WorkspaceIndex=i,
                                            EnableLogging=self._subalgLogging)
            addeeWS = RebinToWorkspace(WorkspaceToRebin=addeeWS,
                                       WorkspaceToMatch=foregroundWS,
                                       OutputWorkspace=addeeWSName,
                                       EnableLogging=self._subalgLogging)
            ys = addeeWS.readY(0)
            foregroundYs += ys
            es = addeeWS.readE(0)
            foregroundEs += es**2
            self._cleanup.cleanup(addeeWS)
        self._cleanup.cleanup(ws)
        numpy.sqrt(foregroundEs, out=foregroundEs)
        # Move the detector to the fractional linePosition
        linePosition = ws.run().getProperty(
            common.SampleLogs.LINE_POSITION).value
        instr = common.instrumentName(ws)
        pixelSize = common.pixelSize(instr)
        dist = pixelSize * (linePosition - beamPosIndex)

        if dist != 0.:
            detPoint1 = ws.spectrumInfo().position(0)
            detPoint2 = ws.spectrumInfo().position(20)
            beta = numpy.math.atan2((detPoint2[0] - detPoint1[0]),
                                    (detPoint2[2] - detPoint1[2]))
            xvsy = numpy.math.sin(beta) * dist
            mz = numpy.math.cos(beta) * dist
            if instr == 'D17':
                mx = xvsy
                my = 0.0
                rotationAxis = [0, 1, 0]
            else:
                mx = 0.0
                my = xvsy
                rotationAxis = [-1, 0, 0]
            MoveInstrumentComponent(Workspace=foregroundWS,
                                    ComponentName='detector',
                                    X=mx,
                                    Y=my,
                                    Z=mz,
                                    RelativePosition=True)
            theta = foregroundWS.spectrumInfo().twoTheta(0) / 2.
            RotateInstrumentComponent(Workspace=foregroundWS,
                                      ComponentName='detector',
                                      X=rotationAxis[0],
                                      Y=rotationAxis[1],
                                      Z=rotationAxis[2],
                                      Angle=theta,
                                      RelativeRotation=True)
        return foregroundWS
    def testAlignComponentsPosition(self):
        r"""

        CreateSampleWorkspace here generates one bank of 2x2 pixels. All pixels have a single peak at
        TOF=10000 micro-seconds. The bank is facing the sample, centered along the vertical axis (Y),
        and at a scattering angle from the beam axis (Z).
        Because the pixels are at different locations, converting units to d-spacing results in peaks at different
        values of d-spacing. For the bank in this test we have:

            workspace index       | 0      | 1      | 2      | 3      |
            ----------------------|--------|--------|--------|--------|
            detector ID           | 4      | 5      | 6      | 6      |
            ----------------------|--------|--------|--------|--------|
            peack-center TOF      |10000   | 10000  | 10000  | 10000  |
            ----------------------|--------|--------|--------|--------|
            peak-center d-spacing | 5.2070 | 5.2070 | 5.1483 | 5.1483 |

        The first workspace index corresponds to a pixel centered on the beam axis, hence the scattering angle is zero
        and the corresponding d-spacing is infinite.

        Correspondence betwee
        """

        def serve_instrument(output_workspace):
            scattering_angle = 20  # in degrees
            # Instrument with four spectra. Each spectrum has one single Gaussian peak in TOF, centered at 10000
            CreateSampleWorkspace(OutputWorkspace=output_workspace, BinWidth=0.1, NumBanks=1, BankPixelWidth=2,
                                  Function='User Defined',
                                  UserDefinedFunction='name=Gaussian, PeakCentre=10000, Height=100, Sigma=2',
                                  Xmin=9900, Xmax=10100, BankDistanceFromSample=2, SourceDistanceFromSample=20)
            MoveInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', RelativePosition=False,
                                    X=0, Y=0.0, Z=0)  # move to the origin
            RotateInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', X=0, Y=1, Z=0,
                                      Angle=scattering_angle, RelativeRotation=False)
            sin, cos = np.sin(np.radians(scattering_angle)), np.cos(np.radians(scattering_angle))
            # detector pixel width is 0.008m, perpendicular to the scattered beam. Detector is 2m away from the sample
            z, x = 2 * cos + 0.004 * sin, 2 * sin - 0.004 * cos
            MoveInstrumentComponent(Workspace=output_workspace, ComponentName='bank1', RelativePosition=False,
                                    X=x, Y=0, Z=z)  # translate 2 meters away and center the detector
            return mtd['original']

        serve_instrument('original')
        # Convert to d-spacing
        ConvertUnits(InputWorkspace='original', Target='dSpacing', EMode='Elastic', OutputWorkspace='original_dspacing')
        # Find the bin boundaries limiting the peak maximum
        Max(InputWorkspace='original_dspacing', OutputWorkspace='original_d_at_max')
        # Average the two bin boundaries to find the bin center, taken to be the location of the peak center
        original_d = np.average(mtd['original_d_at_max'].extractX(), axis=1)

        peak_positions = [5.1483, 5.2070]  # reference peak positions in d-spacing (Angstroms)

        # Generate a table of peak centers in TOF units
        table_tofs = CreateEmptyTableWorkspace(OutputWorkspace='table_tofs')
        column_info = [('int', 'detid'), ('double', '@5.1483'), ('double', '@5.2070')]
        [table_tofs.addColumn(c_type, c_name) for c_type, c_name in column_info]
        table_tofs.addRow([4, float('nan'), 10000.0])
        table_tofs.addRow([5, float('nan'), 10000.0])  # a peak in TOF correspoding to a peak of 306.5928 Angstroms
        table_tofs.addRow([6, 10000.0, float('nan')])  # a peak in TOF correspoding to a peak of 306.5928 Angstroms
        table_tofs.addRow([7, 10000.0, float('nan')])  # a peak in TOF correspoding to a peak of 216.7940 Angstroms

        # perturb the position of the bank with a translation or the order of a few milimeters
        component = 'bank1'
        xyz_shift = V3D(0.005, 0.010, 0.007)
        CloneWorkspace(InputWorkspace='original', OutputWorkspace='perturbed')
        MoveInstrumentComponent(Workspace='perturbed', ComponentName='bank1', RelativePosition=True,
                                X=xyz_shift.X(), Y=xyz_shift.Y(), Z=xyz_shift.Z())

        # calibrate the perturbed bank
        AlignComponents(PeakCentersTofTable='table_tofs', PeakPositions=peak_positions, OutputWorkspace='calibrated',
                        InputWorkspace='perturbed', ComponentList=component, AdjustmentsTable='adjustments',
                        Xposition=True, Yposition=True, Zposition=True)

        # compare the peak-centers (in d-spacing units) between the original and calibrated
        # spectra, up to 0.001 Angstroms
        ConvertUnits(InputWorkspace='calibrated', Target='dSpacing', EMode='Elastic',
                     OutputWorkspace='calibrated_dspacing')
        Max(InputWorkspace='calibrated_dspacing', OutputWorkspace='calibrated_d_at_max')
        calibrated_d = np.average(mtd['calibrated_d_at_max'].extractX(), axis=1)
        assert np.allclose(original_d, calibrated_d, atol=0.001)

        # perturb the orientation of the bank with a rotation of a small angle around an axis almost parallel
        # to the vertical
        axis_shift, angle_shift = V3D(0.2, 0.8, np.sqrt(1 - 0.2 ** 2 - 0.8 ** 2)), 9.0  # angle shift in degrees
        CloneWorkspace(InputWorkspace='original', OutputWorkspace='perturbed')
        RotateInstrumentComponent(Workspace='perturbed', ComponentName='bank1', RelativeRotation=True,
                                  X=axis_shift.X(), Y=axis_shift.Y(), Z=axis_shift.Z(), Angle=angle_shift)
        ConvertUnits(InputWorkspace='perturbed', OutputWorkspace='perturbed_dspacing', Target='dSpacing', Emode='Elastic')

        # calibrate the perturbed bank
        AlignComponents(PeakCentersTofTable='table_tofs', PeakPositions=peak_positions, OutputWorkspace='calibrated',
                        InputWorkspace='perturbed', ComponentList=component, AdjustmentsTable='adjustments',
                        AlphaRotation=True, BetaRotation=True, GammaRotation=True)
        ConvertUnits(InputWorkspace='calibrated', OutputWorkspace='calibrated_dspacing', Target='dSpacing', Emode='Elastic')

        # compare the peak-centers (in d-spacing units) between the original and calibrated
        # spectra, up to 0.001 Angstroms
        Max(InputWorkspace='calibrated_dspacing', OutputWorkspace='calibrated_d_at_max')
        calibrated_d = np.average(mtd['calibrated_d_at_max'].extractX(), axis=1)
        assert np.allclose(original_d, calibrated_d, atol=0.001)
 def get_target_instrument(self):
     self.ws_target = "ws_target"
     LoadEmptyInstrument(Filename="CORELLI_Definition.xml",
                         OutputWorkspace=self.ws_target)
     ConvertToEventWorkspace(InputWorkspace=self.ws_target,
                             OutputWorkspace=self.ws_target)
     # explicitly translate component TO designated location
     MoveInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="moderator",
         X=0,
         Y=0,
         Z=-19.9997,
         RelativePosition=False,
     )
     MoveInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="sample-position",
         X=0,
         Y=0,
         Z=0,
         RelativePosition=False,
     )
     MoveInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank7/sixteenpack",
         X=2.25637,
         Y=-0.814864,
         Z=-0.883485,
         RelativePosition=False,
     )
     MoveInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank42/sixteenpack",
         X=2.58643,
         Y=0.0725628,
         Z=0.0868798,
         RelativePosition=False,
     )
     MoveInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank57/sixteenpack",
         X=0.4545,
         Y=0.0788326,
         Z=2.53234,
         RelativePosition=False,
     )
     # explicitly rotate component TO designated orientation
     RotateInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank7/sixteenpack",
         X=-0.0244456,
         Y=-0.99953,
         Z=-0.0184843,
         Angle=69.4926,
         RelativeRotation=False,
     )
     RotateInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank42/sixteenpack",
         X=-0.011362,
         Y=-0.999935,
         Z=-0.000173303,
         Angle=91.8796,
         RelativeRotation=False,
     )
     RotateInstrumentComponent(
         Workspace=self.ws_target,
         ComponentName="bank42/sixteenpack",
         X=-0.0158497,
         Y=-0.999694,
         Z=0.0189818,
         Angle=169.519,
         RelativeRotation=False,
     )