def provideTheExpectedValue(filename): """ Giving the expected value for the position of the peaks in pixel. The :func:`~Examples.minimalInput` let to the calibrate to guess the position of the pixels among the tubes. Altough it works nicelly, providing these expected values may improve the results. This is done through the **fitPar** parameter. """ from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar) # == Apply the Calibation == ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable)
def _get_fit_par(self, args, tube_set, ideal_tube): if self.FITPAR in args: fit_par = args[self.FITPAR] # fitPar must be a TubeCalibFitParams if not isinstance(fit_par, TubeCalibFitParams): raise RuntimeError( "Wrong argument {0}. This argument, when given, must be a valid TubeCalibFitParams object". format(self.FITPAR)) else: # create a fit parameters guessing centre positions # the guessing obeys the following rule: # # centre_pixel = known_pos * ndets/tube_length + ndets / 2 # # Get tube length and number of detectors tube_length = tube_set.getTubeLength(0) # ndets = len(wsp_index_for_tube0) dummy_id1, ndets, dummy_step = tube_set.getDetectorInfoFromTube(0) known_pos = ideal_tube.getArray() # position of the peaks in pixels centre_pixel = known_pos * ndets / tube_length + ndets * 0.5 fit_par = TubeCalibFitParams(centre_pixel) # make it automatic, it means, that for every tube, # the parameters for fit will be re-evaluated, from the first # guess positions given by centre_pixel fit_par.setAutomatic(True) return fit_par
def improvingCalibrationOfListOfTubes(filename): """ Analysing the result of provideTheExpectedValue it was seen that the calibration of some tubes was not good. .. note:: This method list some of them, there are a group belonging to window B2 that shows only 2 peaks that are not dealt with here. If first plot the bad ones using the **plotTube** option. It them, find where they fail, and how to correct their peaks, using the **overridePeaks**. If finally, applies the calibration again with the points corrected. """ from tube_calib_fit_params import TubeCalibFitParams not_good = [19,37, 71, 75, 181, 186, 234, 235, 245, 273, 345] CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == #calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, # fitPar=fitPar, outputPeak=True, plotTube=not_good, rangeList=not_good) #CalibInstWS = loadingStep(filename) # it is defined as the mean values around the neighbours define_peaks = {19:[10, 80.9771, 123.221, 164.993, 245.717], # the first one was bad 37: [6.36, 80.9347, 122.941, 165.104, 248.32], # the first one was bad 71: [8.62752, 85.074, 124.919, 164.116, 246.82 ], # the last one was bad - check if we can inprove 75: [14.4285, 90.087, 128.987, 167.047, 242.62], # the last one was bad - check if we can inprove 181: [11.726, 94.0496, 137.816, 180, 255], # the third peak was lost 186:[11.9382, 71.5203, 107, 147.727, 239.041], #lost the second peak 234: [4.84, 82.7824, 123.125, 163.945, 241.877], # the first one was bad 235: [4.84, 80.0077, 121.002, 161.098, 238.502], # the first one was bad 245: [9.88089, 93.0593, 136.911, 179.5, 255], # the third peak was bad 273: [18.3711, 105.5, 145.5, 181.6, 243.252], # lost first and third peaks 345: [4.6084, 87.0351, 128.125, 169.923, 245.3] # the last one was bad } calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True, overridePeaks=define_peaks) ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable)
def changeMarginAndExpectedValue(filename): """ To fit correcly, it is important to have a good window around the peak. This windown is defined by the **margin** parameter. This examples shows how the results worsen if we change the margin from its default value **15** to **10**. It shows how to see the fitted values using the **plotTube** parameter. It will also output the peaks position and save them, through the **outputPeak** option and the :func:`tube.savePeak` method. An example of the fitted data compared to the acquired data to find the peaks positions: .. image:: /images/calibratePlotFittedData.png The result deteriorate, as you can see: .. image:: /images/calibrateChangeMarginAndExpectedValue.png """ from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, plotTube=[1,10,100], outputPeak=True, margin=10) # == Apply the Calibation == ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable) tube.savePeak(peakTable, 'TubeDemoMaps01.txt')
def calibrateB2Window(filename): """ There are among the B2 window tubes, some tubes that are showing only 2 strips. Those tubes must be calibrated separated, as the known positions are not valid. This example calibrate them, using only 4 known values: 2 edges and 2 peaks. Run this example, and them see the worksapce in the calibrated instrument and you will see how it worked. The picture shows the output, look that only a section of the B2 Window was calibrated. .. image:: /images/calibrateB2Window.png """ from tube_calib_fit_params import TubeCalibFitParams # b2 with 2 peaks range b2_range = list(range(196, 212)) + list(range(222, 233)) CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole instrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50, -0.16, 0.16, 0.50], [2, 1, 1, 2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True, plotTube=[b2_range[0], b2_range[-1]], rangeList=b2_range) mantid.ApplyCalibration(Workspace=CalibInstWS, PositionTable=calibrationTable)
def completeCalibration(filename): """ This example shows how to use some properties of calibrate method to join together the calibration done in :func:`provideTheExpectedValue`, and improved in :func:`calibrateB2Window`, and :func:`improvingCalibrationOfListOfTubes`. It also improves the result of the calibration because it deals with the E door. The aquired data cannot be used to calibrate the E door, and trying to do so, produces a bad result. In this example, the tubes inside the E door are excluded to the calibration. Using the '''rangeList''' option. """ # first step, load the workspace from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) #execute the improvingCalibrationOfListOfTubes excluding the range of b2 window # correct the definition of the peaks for the folowing indexes #define_peaks = {19:[10, 80.9771, 123.221, 164.993, 245.717], # the first one was bad # 37: [6.36, 80.9347, 122.941, 165.104, 248.32], # the first one was bad # 71: [8.62752, 85.074, 124.919, 164.116, 246.82 ], # the last one was bad - check if we can inprove # 75: [14.4285, 90.087, 128.987, 167.047, 242.62], # the last one was bad - check if we can inprove # 181: [11.726, 94.0496, 137.816, 180, 255], # the third peak was lost # 186:[11.9382, 71.5203, 107, 147.727, 239.041], #lost the second peak # 234: [4.84, 82.7824, 123.125, 163.945, 241.877], # the first one was bad # 235: [4.84, 80.0077, 121.002, 161.098, 238.502], # the first one was bad # 245: [9.88089, 93.0593, 136.911, 179.5, 255], # the third peak was bad # 273: [18.3711, 105.5, 145.5, 181.6, 243.252],# lost first and third peaks # 345: [4.6084, 87.0351, 128.125, 169.923, 245.3]} # the last one was bad define_peaks = {19:[10, 80.9771, 123.221, 164.993, 245.717],\ 37: [6.36, 80.9347, 122.941, 165.104, 248.32],\ 71: [8.62752, 85.074, 124.919, 164.116, 246.82 ],\ 75: [14.4285, 90.087, 128.987, 167.047, 242.62],\ 181: [11.726, 94.0496, 137.816, 180, 255],\ 186:[11.9382, 71.5203, 107, 147.727, 239.041],\ 234: [4.84, 82.7824, 123.125, 163.945, 241.877],\ 235: [4.84, 80.0077, 121.002, 161.098, 238.502],\ 245: [9.88089, 93.0593, 136.911, 179.5, 255],\ 273: [18.3711, 105.5, 145.5, 181.6, 243.252],\ 345: [4.6084, 87.0351, 128.125, 169.923, 245.3]} b2_window = range(196,212) + range(222,233) complete_range = range(648) # this data can not be used to calibrate the E1 window, so, let's remove it. e1_window = range(560,577) aux = numpy.setdiff1d(complete_range, b2_window) # the group that have 3 stripts are all the tubes except the b2 window and e window. range_3_strips = numpy.setdiff1d(aux, e1_window) calibrationTable, peak3Table= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor,\ fitPar=fitPar, outputPeak=True, overridePeaks=define_peaks, rangeList=range_3_strips) # now calibrate the b2_window REMOVE SECOND PEAK # define the known positions and function factor (edge, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16, 0.16, 0.50 ],[2,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # apply the calibration for the b2_window 2 strips values calibrationTable, peak2Table = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, #these parameters now have only 4 points funcFactor, fitPar=fitPar, outputPeak=True, calibTable = calibrationTable, # it will append to the calibTable rangeList = b2_window) ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable)
def findThoseTubesThatNeedSpecialCareForCalibration(filename): """ The example :func:`provideTheExpectedValue` has shown its capability to calibrate almost all tubes, but, as explored in the :func:`improvingCalibrationOfListOfTubes` and :func:`improvingCalibrationSingleTube` there are some tubes that could not be calibrated using that method. The goal of this method is to show one way to find the tubes that will require special care. It will first perform the same calibration seen in :func:`provideTheExpectedValue`, them, it will process the **peakTable** output of the calibrate method when enabling the parameter **outputPeak**. It them creates the Peaks workspace, that is the diffence of the peaks position from the expected values of the peaks positions for all the tubes. This allows to spot what are the tubes whose fitting are outliers in relation to the others. .. image:: /images/plotingPeaksDifference.png The final result for this method is to output using **plotTube** the result of the fitting to all the 'outliers' tubes. """ from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True) # == now, lets investigate the peaks #parsing the peakTable to produce a numpy array with dimension (number_of_tubes x number_of_peaks) print 'parsing the peak table' n = len(peakTable) peaksId = n*[''] data = numpy.zeros((n,5)) line = 0 for row in peakTable: data_row = [row['Peak%d'%(i)] for i in [1,2,3,4,5]] data[line,:] = data_row peaksId[line] = row['TubeId'] line+=1 # data now has all the peaks positions for each tube # the mean value is the expected value for the peak position for each tube expected_peak_pos = numpy.mean(data,axis=0) #calculate how far from the expected position each peak position is distance_from_expected = numpy.abs(data - expected_peak_pos) print 'Creating the Peaks Workspace that shows the distance from the expected value for all peaks for each tube' # Let's see these peaks: Peaks = CreateWorkspace(range(n),distance_from_expected,NSpec=5) # plot all the 5 peaks for Peaks Workspace. You will see that most of the tubes differ # at most 12 pixels from the expected values. #so let's investigate those that differ more than 12 # return an array with the indexes for the first axis which is the tube indentification check = numpy.where(distance_from_expected > 12)[0] #remove repeated values #select only those tubes inside the problematic_tubes problematic_tubes = list(set(check)) print 'Tubes whose distance is far from the expected value: ', problematic_tubes print 'Calibrating again only these tubes' #let's confir that our suspect works CalibInstWS = loadingStep(filename) calibrationTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor,\ fitPar=fitPar, rangeList= problematic_tubes, plotTube=problematic_tubes)
def improvingCalibrationSingleTube(filename): """ The :func:`~Examples.provideTheExpectedValue` provided a good solution, but there are few tubes whose calibration was not so good. This method explores how to deal with these tubes. First of all, it is important to identify the tubes that did not work well. From the outputs of provideTheExpectedValue, looking inside the instrument tree, it is possible to list all the tubes that are not so good. Unfortunatelly, they do not have a single name identifier. So, locating them it is a little bit trickier. The :func:`~Examples.findThoseTubesThatNeedSpecialCareForCalibration` shows one way of finding those tubes. The index is the position inside the PeakTable. For this example, we have used inspection from the Instrument View. One of them is inside the A1_Window, 3rd PSD_TUBE_STRIP 8 pack up, 4th PSD_TUBE_STRIP: Index = 8+8+4 - 1 = 19. In this example, we will ask the calibration to run the calibration only for 3 tubes (indexes 18,19,20). Them, we will check why the 19 is not working well. Finally, we will try to provide another peaks position for this tube, and run the calibration again for these tubes, to improve the results. This example shows how to use **overridePeaks** option """ from tube_calib_fit_params import TubeCalibFitParams import time CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True, plotTube=[18,19,20], rangeList=[18,19,20]) ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable) # reload to reset the calibration applied CalibInstWS = loadingStep(filename) # looking into the second line of calibrationTable, you will see that it defines the peaks for the first position # as 14.9788, -0.303511, 9.74828 # let's change the peak from -0.303511 to 8.14 #to override the peaks definition, we use the overridePeaks overridePeaks = {19: [8.14, 80.9771, 123.221, 164.993, 245.717]} # == Get the calibration and put results into calibration table == # we will not plot anymore, because it will not plot the overrided peaks calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True, rangeList=[18,19,20], overridePeaks=overridePeaks) ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable)
def findThoseTubesThatNeedSpecialCareForCalibration(filename): """ The example :func:`provideTheExpectedValue` has shown its capability to calibrate almost all tubes, but, as explored in the :func:`improvingCalibrationOfListOfTubes` and :func:`improvingCalibrationSingleTube` there are some tubes that could not be calibrated using that method. The goal of this method is to show one way to find the tubes that will require special care. It will first perform the same calibration seen in :func:`provideTheExpectedValue`, them, it will process the **peakTable** output of the calibrate method when enabling the parameter **outputPeak**. It them creates the Peaks workspace, that is the diffence of the peaks position from the expected values of the peaks positions for all the tubes. This allows to spot what are the tubes whose fitting are outliers in relation to the others. .. image:: /images/plotingPeaksDifference.png The final result for this method is to output using **plotTube** the result of the fitting to all the 'outliers' tubes. """ from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # == Get the calibration and put results into calibration table == calibrationTable, peakTable= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True) # == now, lets investigate the peaks #parsing the peakTable to produce a numpy array with dimension (number_of_tubes x number_of_peaks) print 'parsing the peak table' n = len(peakTable) peaksId = n*[''] data = numpy.zeros((n,5)) line = 0 for row in peakTable: data_row = [row['Peak%d'%(i)] for i in [1,2,3,4,5]] data[line,:] = data_row peaksId[line] = row['TubeId'] line+=1 # data now has all the peaks positions for each tube # the mean value is the expected value for the peak position for each tube expected_peak_pos = numpy.mean(data,axis=0) #calculate how far from the expected position each peak position is distance_from_expected = numpy.abs(data - expected_peak_pos) print 'Creating the Peaks Workspace that shows the distance from the expected value for all peaks for each tube' # Let's see these peaks: Peaks = CreateWorkspace(range(n),distance_from_expected,NSpec=5) # plot all the 5 peaks for Peaks Workspace. You will see that most of the tubes differ # at most 12 pixels from the expected values. #so let's investigate those that differ more than 12 # return an array with the indexes for the first axis which is the tube indentification check = numpy.where(distance_from_expected > 12)[0] #remove repeated values #select only those tubes inside the problematic_tubes problematic_tubes = list(set(check)) print 'Tubes whose distance is far from the expected value: ', problematic_tubes print 'Calibrating again only these tubes' #let's confir that our suspect works CalibInstWS = loadingStep(filename) calibrationTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, rangeList= problematic_tubes, plotTube=problematic_tubes)
def calibrate(ws, tubeSet, knownPositions, funcForm, **kwargs): """ Define the calibrated positions of the detectors inside the tubes defined in tubeSet. Tubes may be considered a list of detectors alined that may be considered as pixels for the analogy when they values are displayed. The position of these pixels are provided by the manufactor, but its real position depends on the electronics inside the tube and varies slightly from tube to tube. The calibrate method, aims to find the real positions of the detectors (pixels) inside the tube. For this, it will receive an Integrated workspace, where a special measurement was performed so to have a pattern of peaks or through. Where gaussian peaks or edges can be found. The calibration follows the following steps 1. Finding the peaks on each tube 2. Fitting the peaks agains the Known Positions 3. Defining the new position for the pixels(detectors) Let's consider the simplest way of calling calibrate: .. code-block:: python from tube import calibrate ws = Load('WISH17701') ws = Integration(ws) known_pos = [-0.41,-0.31,-0.21,-0.11,-0.02, 0.09, 0.18, 0.28, 0.39 ] peaks_form = 9*[1] # all the peaks are gaussian peaks calibTable = calibrate(ws,'WISH/panel03',known_pos, peaks_form) In this example, the calibrate framework will consider all the tubes (152) from WISH/panel03. You may decide to look for a subset of the tubes, by passing the **rangeList** option. .. code-block:: python # This code will calibrate only the tube indexed as number 3 # (usually tube0004) calibTable = calibrate(ws,'WISH/panel03',known_pos, peaks_form, rangeList=[3]) **Finding the peaks on each tube** * Dynamically fitting peaks The framework expects that for each tube, it will find a peak pattern around the pixels corresponding to the known_pos positions. The way it will work out the estimated peak position (in pixel) is 1. Get the length of the tube: distance(first_detector,last_detector) in the tube. 2. Get the number of detectors in the tube (nDets) 3. It will be assumed that the center of the tube correspond to the origin (0) .. code-block:: python centre_pixel = known_pos * nDets/tube_length + nDets/2 It will them look for the real peak around the estimated value as: .. code-block:: python # consider tube_values the array of counts, and peak the estimated # position for the peak real_peak_pos = argmax(tube_values[peak-margin:peak+margin]) After finding the real_peak_pos, it will try to fit the region around the peak to find the best expected position of the peak in a continuous space. It will do this by fitting the region around the peak to a Gaussian Function, and them extract the PeakCentre returned by the Fitting. .. code-block:: python centre = real_peak_pos fit_start, fit_stop = centre-margin, centre+margin values = tube_values[fit_start,fit_stop] background = min(values) peak = max(values) - background width = len(where(values > peak/2+background)) # It will fit to something like: # Fit(function=LinerBackground,A0=background;Gaussian, # Height=peak, PeakCentre=centre, Sigma=width,fit_start,fit_end) * Force Fitting Parameters These dinamically values can be avoided by defining the **fitPar** for the calibrate function .. code-block:: python eP = [57.5, 107.0, 156.5, 206.0, 255.5, 305.0, 354.5, 404.0, 453.5] # Expected Height of Gaussian Peaks (initial value of fit parameter) ExpectedHeight = 1000.0 # Expected width of Gaussian peaks in pixels # (initial value of fit parameter) ExpectedWidth = 10.0 fitPar = TubeCalibFitParams( eP, ExpectedHeight, ExpectedWidth ) calibTable = calibrate(ws, 'WISH/panel03', known_pos, peaks_form, fitPar=fitPar) Different Function Factors Although the examples consider only Gaussian peaks, it is possible to change the function factors to edges by passing the index of the known_position through the **funcForm**. Hence, considering three special points, where there are one gaussian peak and thow edges, the calibrate could be configured as: .. code-block:: python known_pos = [-0.1 2 2.3] # gaussian peak followed by two edges (through) form_factor = [1 2 2] calibTable = calibrate(ws,'WISH/panel03',known_pos, form_factor) * Override Peaks It is possible to scape the finding peaks position steps by providing the peaks through the **overridePeaks** parameters. The example below tests the calibration of a single tube (30) but scapes the finding peaks step. .. code-block:: python known_pos = [-0.41,-0.31,-0.21,-0.11,-0.02, 0.09, 0.18, 0.28, 0.39 ] define_peaks = [57.5, 107.0, 156.5, 206.0, 255.5, 305.0, 354.5, 404.0, 453.5] calibTable = calibrate(ws, 'WISH/panel03', known_pos, peaks_form, overridePeaks={30:define_peaks}, rangeList=[30]) * Output Peaks Positions Enabling the option **outputPeak** a WorkspaceTable will be produced with the first column as tube name and the following columns with the position where corresponding peaks were found. Like the table below. +-------+-------+-----+-------+ |TubeId | Peak1 | ... | PeakM | +=======+=======+=====+=======+ |tube0 | 15.5 | ... | 370.3 | +-------+-------+-----+-------+ | ... | ... | ... | ... | +-------+-------+-----+-------+ |tubeN | 14.9 | ... | 371.2 | +-------+-------+-----+-------+ The signature changes to: .. code-block:: python calibTable, peakTable = calibrate(...) It is possible to give a peakTable directly to the **outputPeak** option, which will make the calibration to append the peaks to the given table. .. hint:: It is possible to save the peakTable to a file using the :meth:`savePeak` method. **Find the correct position along the tube** The second step of the calibration is to define the correct position of pixels along the tube. This is done by fitting the peaks positions found at the previous step against the known_positions provided. :: known | * positions | * | * | * |________________ pixels positions The default operation is to fit the pixels positions against the known positions with a quadratic function in order to define an operation to move all the pixels to their real positions. If necessary, the user may select to fit using a polinomial of 3rd order, through the parameter **fitPolyn**. .. note:: The known positions are given in the same unit as the spacial position (3D) and having the center of the tube as the origin. Hence, this section will define a function that: .. math:: F(pix) = RealRelativePosition **Define the new position for the detectors** Finally, the position of the detectors are defined as a vector operation like .. math:: \\vec{p} = \\vec{c} + v \\vec{u} Where :math:`\\vec{p}` is the position in the 3D space, **v** is the RealRelativePosition deduced from the last session, and finally, :math:`\\vec{u}` is the unitary vector in the direction of the tube. :param ws: Integrated workspace with tubes to be calibrated. :param tubeSet: Specification of Set of tubes to be calibrated. If a string is passed, a TubeSpec will be created passing the string as the setTubeSpecByString. This will be the case for TubeSpec as string .. code-block:: python self.tube_spec = TubeSpec(ws) self.tube_spec.setTubeSpecByString(tubeSet) If a list of strings is passed, the TubeSpec will be created with this list: .. code-block:: python self.tube_spec = TubeSpec(ws) self.tube_spec.setTubeSpecByStringArray(tubeSet) If a :class:`~tube_spec.TubeSpec` object is passed, it will be used as it is. :param knownPositions: The defined position for the peaks/edges, taking the center as the origin and having the same units as the tube length in the 3D space. :param funcForm: list with special values to define the format of the peaks/edge (peaks=1, edge=2). If it is not provided, it will be assumed that all the knownPositions are peaks. Optionals parameters to tune the calibration: :param fitPar: Define the parameters to be used in the fit as a :class:`~tube_calib_fit_params.TubeCalibFitParams`. If not provided, the dynamic mode is used. See :py:func:`~Examples.TubeCalibDemoMaps_All.provideTheExpectedValue` :param margin: value in pixesl that will be used around the peaks/edges to fit them. Default = 15. See the code of :py:mod:`~Examples.TubeCalibDemoMerlin` where **margin** is used to calibrate small tubes. .. code-block:: python fit_start, fit_end = centre - margin, centre + margin :param rangeList: list of tubes indexes that will be calibrated. As in the following code (see: :py:func:`~Examples.TubeCalibDemoMaps_All.improvingCalibrationSingleTube`): .. code-block:: python for index in rangelist: do_calibrate(tubeSet.getTube(index)) :param calibTable: Pass the calibration table, it will them append the values to the provided one and return it. (see: :py:mod:`~Examples.TubeCalibDemoMerlin`) :param plotTube: If given, the tube whose index is in plotTube will be ploted as well as its fitted peaks, it can receive a list of indexes to plot.(see: :py:func:`~Examples.TubeCalibDemoMaps_All.changeMarginAndExpectedValue`) :param excludeShortTubes: Do not calibrate tubes whose length is smaller than given value. (see at: Examples/TubeCalibDemoMerlin_Adjustable.py) :param overridePeaks: dictionary that defines an array of peaks positions (in pixels) to be used for the specific tube(key). (see: :py:func:`~Examples.TubeCalibDemoMaps_All.improvingCalibrationSingleTube`) .. code-block:: python for index in rangelist: if overridePeaks.has_key(index): use_this_peaks = overridePeaks[index] # skip finding peaks fit_peaks_to_position() :param fitPolyn: Define the order of the polinomial to fit the pixels positions agains the known positions. The acceptable values are 1, 2 or 3. Default = 2. :param outputPeak: Enable the calibrate to output the peak table, relating the tubes with the pixels positions. It may be passed as a boolean value (outputPeak=True) or as a peakTable value. The later case is to inform calibrate to append the new values to the given peakTable. This is usefull when you have to operate in subsets of tubes. (see :py:mod:`~Examples.TubeCalibDemoMerlin` that shows a nice inspection on this table). .. code-block:: python calibTable, peakTable = calibrate(ws, (omitted), rangeList=[1], outputPeak=True) # appending the result to peakTable calibTable, peakTable = calibrate(ws, (omitted), rangeList=[2], outputPeak=peakTable) # now, peakTable has information for tube[1] and tube[2] :rtype: calibrationTable, a TableWorkspace with two columns DetectorID(int) and DetectorPositions(V3D). """ FITPAR = 'fitPar' MARGIN = 'margin' RANGELIST = 'rangeList' CALIBTABLE = 'calibTable' PLOTTUBE = 'plotTube' EXCLUDESHORT = 'excludeShortTubes' OVERRIDEPEAKS = 'overridePeaks' FITPOLIN = 'fitPolyn' OUTPUTPEAK = 'outputPeak' #check that only valid arguments were passed through kwargs for key in kwargs.keys(): if key not in [FITPAR, MARGIN, RANGELIST, CALIBTABLE, PLOTTUBE, EXCLUDESHORT, OVERRIDEPEAKS, FITPOLIN, OUTPUTPEAK]: msg = "Wrong argument: '%s'! This argument is not defined in the signature of this function. Hint: remember that arguments are case sensitive" % key raise RuntimeError(msg) # check parameter ws: if it was given as string, transform it in # mantid object if isinstance(ws,str): ws = mtd[ws] if not isinstance(ws,MatrixWorkspace): raise RuntimeError("Wrong argument ws = %s. It must be a MatrixWorkspace" % (str(ws))) # check parameter tubeSet. It accepts string or preferable a TubeSpec if isinstance(tubeSet,str): selectedTubes = tubeSet tubeSet = TubeSpec(ws) tubeSet.setTubeSpecByString(selectedTubes) elif isinstance(tubeSet, list): selectedTubes = tubeSet tubeSet = TubeSpec(ws) tubeSet.setTubeSpecByStringArray(selectedTubes) elif not isinstance(tubeSet,TubeSpec): raise RuntimeError("Wrong argument tubeSet. It must be a TubeSpec or a string that defines the set of tubes to be calibrated. For example: WISH/panel03") # check the known_positions parameter # for old version compatibility, it also accepts IdealTube, eventhough # they should only be used internally if not (isinstance(knownPositions, list) or isinstance(knownPositions, tuple) or isinstance(knownPositions, numpy.ndarray)): raise RuntimeError("Wrong argument knownPositions. It expects a list of values for the positions expected for the peaks in relation to the center of the tube") else: idealTube = IdealTube() idealTube.setArray(numpy.array(knownPositions)) #deal with funcForm parameter try: nPeaks = len(idealTube.getArray()) if len(funcForm) != nPeaks: raise 1 for val in funcForm: if val not in [1,2]: raise 2 except: raise RuntimeError("Wrong argument FuncForm. It expects a list of values describing the form of everysingle peaks. So, for example, if there are three peaks where the first is a peak and the followers as edge, funcForm = [1, 2, 2]. Currently, it is defined 1-Gaussian Peak, 2 - Edge. The knownPos has %d elements and the given funcForm has %d."%(nPeaks, len(funcForm))) #apply the functional form to the ideal Tube idealTube.setForm(funcForm) # check the FITPAR parameter (optional) # if the FITPAR is given, than it will just pass on, if the FITPAR is # not given, it will create a FITPAR 'guessing' the centre positions, # and allowing the find peaks calibration methods to adjust the parameter # for the peaks automatically if kwargs.has_key(FITPAR): fitPar = kwargs[FITPAR] #fitPar must be a TubeCalibFitParams if not isinstance(fitPar, TubeCalibFitParams): raise RuntimeError("Wrong argument %s. This argument, when given, must be a valid TubeCalibFitParams object"%FITPAR) else: # create a fit parameters guessing centre positions # the guessing obeys the following rule: # # centre_pixel = known_pos * ndets/tube_length + ndets / 2 # # Get tube length and number of detectors tube_length = tubeSet.getTubeLength(0) #ndets = len(wsp_index_for_tube0) id1, ndets, step = tubeSet.getDetectorInfoFromTube(0) known_pos = idealTube.getArray() # position of the peaks in pixels centre_pixel = known_pos * ndets/tube_length + ndets * 0.5 fitPar = TubeCalibFitParams(centre_pixel) # make it automatic, it means, that for every tube, # the parameters for fit will be re-evaluated, from the first # guess positions given by centre_pixel fitPar.setAutomatic(True) # check the MARGIN paramter (optional) if kwargs.has_key(MARGIN): try: margin = float(kwargs[MARGIN]) except: raise RuntimeError("Wrong argument %s. It was expected a number!"%MARGIN) fitPar.setMargin(margin) #deal with RANGELIST parameter if kwargs.has_key(RANGELIST): rangeList = kwargs[RANGELIST] if isinstance(rangeList,int): rangeList = [rangeList] try: # this deals with list and tuples and iterables to make sure # rangeList becomes a list rangeList = list(rangeList) except: raise RuntimeError("Wrong argument %s. It expects a list of indexes for calibration"%RANGELIST) else: rangeList = range(tubeSet.getNumTubes()) # check if the user passed the option calibTable if kwargs.has_key(CALIBTABLE): calibTable = kwargs[CALIBTABLE] #ensure the correct type is passed # if a string was passed, transform it in mantid object if isinstance(calibTable,str): calibTable = mtd[calibTable] #check that calibTable has the expected form try: if not isinstance(calibTable,ITableWorkspace): raise 1 if calibTable.columnCount() != 2: raise 2 colNames = calibTable.getColumnNames() if colNames[0] != 'Detector ID' or colNames[1] != 'Detector Position': raise 3 except: raise RuntimeError("Invalid type for %s. The expected type was ITableWorkspace with 2 columns(Detector ID and Detector Positions)" % CALIBTABLE) else: calibTable = CreateEmptyTableWorkspace(OutputWorkspace="CalibTable") # "Detector ID" column required by ApplyCalibration calibTable.addColumn(type="int",name="Detector ID") # "Detector Position" column required by ApplyCalibration calibTable.addColumn(type="V3D",name="Detector Position") #deal with plotTube option if kwargs.has_key(PLOTTUBE): plotTube = kwargs[PLOTTUBE] if isinstance(plotTube, int): plotTube = [plotTube] try: plotTube = list(plotTube) except: raise RuntimeError("Wrong argument %s. It expects an index (int) or a list of indexes" %PLOTTUBE) else: plotTube = [] #deal with minimun tubes sizes if kwargs.has_key(EXCLUDESHORT): excludeShortTubes = kwargs[EXCLUDESHORT] try: excludeShortTubes = float(excludeShortTubes) except: raise RuntimeError("Wrong argument %s. It expects a float value for the minimun size of tubes to be calibrated") else: #a tube with length 0 can not be calibrated, this is the minimun value excludeShortTubes = 0.0 #deal with OVERRIDEPEAKS parameters if kwargs.has_key(OVERRIDEPEAKS): overridePeaks = kwargs[OVERRIDEPEAKS] try: nPeaks = len(idealTube.getArray()) # check the format of override peaks if not isinstance(overridePeaks, dict): raise 1 for key in overridePeaks.keys(): if not isinstance(key,int): raise 2 if key < 0 or key >= tubeSet.getNumTubes(): raise 3 if len(overridePeaks[key]) != nPeaks: raise 4 except: raise RuntimeError("Wrong argument %s. It expects a dictionary with key as the tube index and the value as a list of peaks positions. Ex (3 peaks): overridePeaks = {1:[2,5.4,500]}"%OVERRIDEPEAKS) else: overridePeaks = dict() # deal with FITPOLIN parameter if kwargs.has_key(FITPOLIN): polinFit = kwargs[FITPOLIN] if polinFit not in [1, 2,3]: raise RuntimeError("Wrong argument %s. It expects a number 1 for linear, 2 for quadratic, or 3 for 3rd polinomial order when fitting the pixels positions agains the known positions" % FITPOLIN) else: polinFit = 2 # deal with OUTPUT PEAK deletePeakTableAfter = False if kwargs.has_key(OUTPUTPEAK): outputPeak = kwargs[OUTPUTPEAK] else: outputPeak = False if isinstance(outputPeak, ITableWorkspace): if outputPeak.columnCount() < len(idealTube.getArray()): raise RuntimeError("Wrong argument %s. It expects a boolean flag, or a ITableWorksapce with columns (TubeId, Peak1,...,PeakM) for M = number of peaks given in knownPositions" % OUTPUTPEAK) else: if not outputPeak: deletePeakTableAfter = True # create the output peak table outputPeak = CreateEmptyTableWorkspace(OutputWorkspace="PeakTable") outputPeak.addColumn(type='str',name='TubeId') for i in range(len(idealTube.getArray())): outputPeak.addColumn(type='float',name='Peak%d'%(i+1)) getCalibration(ws, tubeSet, calibTable, fitPar, idealTube, outputPeak,\ overridePeaks, excludeShortTubes, plotTube, rangeList, polinFit) if deletePeakTableAfter: DeleteWorkspace(str(outputPeak)) return calibTable else: return calibTable, outputPeak
filename) #'raw' in 'rawCalibInstWS' means unintegrated. CalibInstWS = mantid.Integration(rawCalibInstWS, RangeLower=1, RangeUpper=20000) mantid.DeleteWorkspace(rawCalibInstWS) print( "Created workspace (CalibInstWS) with integrated data from run and instrument to calibrate" ) CalibratedComponent = 'WISH/panel03/tube038' # Set fitting parameters eP = [65.0, 113.0, 161.0, 209.0, 257.0, 305.0, 353.0, 401.0, 449.0] ExpectedHeight = 2000.0 # Expected Height of Gaussian Peaks (initial value of fit parameter) ExpectedWidth = 32.0 # Expected width of Gaussian peaks in pixels (initial value of fit parameter) fitPar = TubeCalibFitParams(eP, ExpectedHeight, ExpectedWidth) fitPar.setAutomatic(True) print("Created objects needed for calibration.") func_form = 9 * [1] # Use first tube as ideal tube tube1 = TubeSpec(CalibInstWS) tube1.setTubeSpecByString('WISH/panel03/tube038') iTube = tube_calib.constructIdealTubeFromRealTube(CalibInstWS, tube1, fitPar, func_form) known_pos = iTube.getArray() print(known_pos) # Get the calibration and put it into the calibration table calibrationTable = tube.calibrate(CalibInstWS,
def runTest(self): # This script calibrates WISH using known peak positions from # neutron absorbing bands. The workspace with suffix "_calib" # contains calibrated data. The workspace with suxxic "_corrected" # contains calibrated data with known problematic tubes also corrected ws = mantid.LoadNexusProcessed(Filename="WISH30541_integrated.nxs") # This array defines the positions of peaks on the detector in # meters from the center (0) # For wish this is calculated as follows: # Height of all 7 bands = 0.26m => each band is separated by 0.260 / 6 = 0.4333m # The bands are on a cylinder diameter 0.923m. So we can work out the angle as # (0.4333 * n) / (0.923 / 2) where n is the number of bands above (or below) the # center band. # Putting this together with the distance to the detector tubes (2.2m) we get # the following: (0.4333n) / 0.4615 * 2200 = Expected peak positions # From this we can show there should be 5 peaks (peaks 6 + 7 are too high/low) # at: 0, 0.206, 0.413 respectively (this is symmetrical so +/-) peak_positions = np.array([-0.413, -0.206, 0, 0.206, 0.413]) funcForm = 5 * [1] # 5 gaussian peaks fitPar = TubeCalibFitParams([59, 161, 258, 353, 448]) fitPar.setAutomatic(True) instrument = ws.getInstrument() spec = TubeSpec(ws) spec.setTubeSpecByString(instrument.getFullName()) idealTube = IdealTube() idealTube.setArray(peak_positions) # First calibrate all of the detectors calibrationTable, peaks = tube.calibrate(ws, spec, peak_positions, funcForm, margin=15, outputPeak=True, fitPar=fitPar) self.calibration_table = calibrationTable def findBadPeakFits(peaksTable, threshold=10): """ Find peaks whose fit values fall outside of a given tolerance of the mean peak centers across all tubes. Tubes are defined as have a bad fit if the absolute difference between the fitted peak centers for a specific tube and the mean of the fitted peak centers for all tubes differ more than the threshold parameter. @param peakTable: the table containing fitted peak centers @param threshold: the tolerance on the difference from the mean value @return A list of expected peak positions and a list of indices of tubes to correct """ n = len(peaksTable) num_peaks = peaksTable.columnCount() - 1 column_names = ['Peak%d' % i for i in range(1, num_peaks + 1)] data = np.zeros((n, num_peaks)) for i, row in enumerate(peaksTable): data_row = [row[name] for name in column_names] data[i, :] = data_row # data now has all the peaks positions for each tube # the mean value is the expected value for the peak position for each tube expected_peak_pos = np.mean(data, axis=0) # calculate how far from the expected position each peak position is distance_from_expected = np.abs(data - expected_peak_pos) check = np.where(distance_from_expected > threshold)[0] problematic_tubes = list(set(check)) print("Problematic tubes are: " + str(problematic_tubes)) return expected_peak_pos, problematic_tubes def correctMisalignedTubes(ws, calibrationTable, peaksTable, spec, idealTube, fitPar, threshold=10): """ Correct misaligned tubes due to poor fitting results during the first round of calibration. Misaligned tubes are first identified according to a tolerance applied to the absolute difference between the fitted tube positions and the mean across all tubes. The FindPeaks algorithm is then used to find a better fit with the ideal tube positions as starting parameters for the peak centers. From the refitted peaks the positions of the detectors in the tube are recalculated. @param ws: the workspace to get the tube geometry from @param calibrationTable: the calibration table output from running calibration @param peaksTable: the table containing the fitted peak centers from calibration @param spec: the tube spec for the instrument @param idealTube: the ideal tube for the instrument @param fitPar: the fitting parameters for calibration @param threshold: tolerance defining is a peak is outside of the acceptable range @return table of corrected detector positions """ table_name = calibrationTable.name() + 'Corrected' corrections_table = mantid.CreateEmptyTableWorkspace( OutputWorkspace=table_name) corrections_table.addColumn('int', "Detector ID") corrections_table.addColumn('V3D', "Detector Position") mean_peaks, bad_tubes = findBadPeakFits(peaksTable, threshold) for index in bad_tubes: print("Refitting tube %s" % spec.getTubeName(index)) tube_dets, _ = spec.getTube(index) getPoints(ws, idealTube.getFunctionalForms(), fitPar, tube_dets) tube_ws = mantid.mtd['TubePlot'] fit_ws = mantid.FindPeaks(InputWorkspace=tube_ws, WorkspaceIndex=0, PeakPositions=fitPar.getPeaks(), PeaksList='RefittedPeaks') centers = [row['centre'] for row in fit_ws] detIDList, detPosList = getCalibratedPixelPositions( ws, centers, idealTube.getArray(), tube_dets) for id, pos in zip(detIDList, detPosList): corrections_table.addRow({ 'Detector ID': id, 'Detector Position': kernel.V3D(*pos) }) return corrections_table corrected_calibration_table = correctMisalignedTubes( ws, calibrationTable, peaks, spec, idealTube, fitPar) self.correction_table = corrected_calibration_table tube.saveCalibration(self.correction_table.getName(), out_path=self.calibration_out_path) tube.saveCalibration(self.calibration_table.getName(), out_path=self.correction_out_path)
RangeLower=rangeLower, RangeUpper=rangeUpper) mantid.DeleteWorkspace(rawCalibInstWS) print( "Created workspace (CalibInstWS) with integrated data from run and instrument to calibrate" ) # == Create Objects needed for calibration == # The positions of the shadows and ends here are an intelligent guess. # First array gives positions in Metres and second array gives type 1=Gaussian peak 2=edge. # See http://www.mantidproject.org/IdealTube for details. knownPos = [-0.50, -0.165, -0.00, 0.165, 0.50] funcForm = [2, 1, 1, 1, 2] # Get fitting parameters fitPar = TubeCalibFitParams(ExpectedPositions, ExpectedHeight, ExpectedWidth) fitPar.setAutomatic(True) print("Created objects needed for calibration.") # == Get the calibration and put results into calibration table == # also put peaks into PeakFile calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcForm, fitPar=fitPar, outputPeak=True) print("Got calibration (new positions of detectors) ") # == Apply the Calibation ==
def completeCalibration(filename): """ This example shows how to use some properties of calibrate method to join together the calibration done in :func:`provideTheExpectedValue`, and improved in :func:`calibrateB2Window`, and :func:`improvingCalibrationOfListOfTubes`. It also improves the result of the calibration because it deals with the E door. The aquired data cannot be used to calibrate the E door, and trying to do so, produces a bad result. In this example, the tubes inside the E door are excluded to the calibration. Using the '''rangeList''' option. """ # first step, load the workspace from tube_calib_fit_params import TubeCalibFitParams CalibInstWS = loadingStep(filename) # == Set parameters for calibration == # Set what we want to calibrate (e.g whole intrument or one door ) CalibratedComponent = 'MAPS' # Calibrate all # define the known positions and function factor (edge, peak, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16,-0.00, 0.16, 0.50 ],[2,1,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) #execute the improvingCalibrationOfListOfTubes excluding the range of b2 window # correct the definition of the peaks for the folowing indexes #define_peaks = {19:[10, 80.9771, 123.221, 164.993, 245.717], # the first one was bad # 37: [6.36, 80.9347, 122.941, 165.104, 248.32], # the first one was bad # 71: [8.62752, 85.074, 124.919, 164.116, 246.82 ], # the last one was bad - check if we can inprove # 75: [14.4285, 90.087, 128.987, 167.047, 242.62], # the last one was bad - check if we can inprove # 181: [11.726, 94.0496, 137.816, 180, 255], # the third peak was lost # 186:[11.9382, 71.5203, 107, 147.727, 239.041], #lost the second peak # 234: [4.84, 82.7824, 123.125, 163.945, 241.877], # the first one was bad # 235: [4.84, 80.0077, 121.002, 161.098, 238.502], # the first one was bad # 245: [9.88089, 93.0593, 136.911, 179.5, 255], # the third peak was bad # 273: [18.3711, 105.5, 145.5, 181.6, 243.252],# lost first and third peaks # 345: [4.6084, 87.0351, 128.125, 169.923, 245.3]} # the last one was bad define_peaks = {19:[10, 80.9771, 123.221, 164.993, 245.717], 37: [6.36, 80.9347, 122.941, 165.104, 248.32], 71: [8.62752, 85.074, 124.919, 164.116, 246.82 ], 75: [14.4285, 90.087, 128.987, 167.047, 242.62], 181: [11.726, 94.0496, 137.816, 180, 255], 186:[11.9382, 71.5203, 107, 147.727, 239.041], 234: [4.84, 82.7824, 123.125, 163.945, 241.877], 235: [4.84, 80.0077, 121.002, 161.098, 238.502], 245: [9.88089, 93.0593, 136.911, 179.5, 255], 273: [18.3711, 105.5, 145.5, 181.6, 243.252], 345: [4.6084, 87.0351, 128.125, 169.923, 245.3]} b2_window = range(196,212) + range(222,233) complete_range = range(648) # this data can not be used to calibrate the E1 window, so, let's remove it. e1_window = range(560,577) aux = numpy.setdiff1d(complete_range, b2_window) # the group that have 3 stripts are all the tubes except the b2 window and e window. range_3_strips = numpy.setdiff1d(aux, e1_window) calibrationTable, peak3Table= tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcFactor, fitPar=fitPar, outputPeak=True, overridePeaks=define_peaks, rangeList=range_3_strips) # now calibrate the b2_window REMOVE SECOND PEAK # define the known positions and function factor (edge, peak, peak, edge) knownPos, funcFactor = [-0.50,-0.16, 0.16, 0.50 ],[2,1,1,2] # the expected positions in pixels for the special points expectedPositions = [4.0, 85.0, 161.0, 252.0] fitPar = TubeCalibFitParams(expectedPositions) fitPar.setAutomatic(True) # apply the calibration for the b2_window 2 strips values calibrationTable, peak2Table = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, #these parameters now have only 4 points funcFactor, fitPar=fitPar, outputPeak=True, calibTable = calibrationTable, # it will append to the calibTable rangeList = b2_window) ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable)
def runTest(self): # This script calibrates WISH using known peak positions from # neutron absorbing bands. The workspace with suffix "_calib" # contains calibrated data. The workspace with suxxic "_corrected" # contains calibrated data with known problematic tubes also corrected ws = mantid.LoadNexusProcessed(Filename="WISH30541_integrated.nxs") # This array defines the positions of peaks on the detector in # meters from the center (0) # For wish this is calculated as follows: # Height of all 7 bands = 0.26m => each band is separated by 0.260 / 6 = 0.4333m # The bands are on a cylinder diameter 0.923m. So we can work out the angle as # (0.4333 * n) / (0.923 / 2) where n is the number of bands above (or below) the # center band. # Putting this together with the distance to the detector tubes (2.2m) we get # the following: (0.4333n) / 0.4615 * 2200 = Expected peak positions # From this we can show there should be 5 peaks (peaks 6 + 7 are too high/low) # at: 0, 0.206, 0.413 respectively (this is symmetrical so +/-) peak_positions = np.array([-0.413, -0.206, 0, 0.206, 0.413]) funcForm = 5 * [1] # 5 gaussian peaks fitPar = TubeCalibFitParams([59, 161, 258, 353, 448]) fitPar.setAutomatic(True) instrument = ws.getInstrument() spec = TubeSpec(ws) spec.setTubeSpecByString(instrument.getFullName()) idealTube = IdealTube() idealTube.setArray(peak_positions) # First calibrate all of the detectors calibrationTable, peaks = tube.calibrate(ws, spec, peak_positions, funcForm, margin=15, outputPeak=True, fitPar=fitPar) self.calibration_table = calibrationTable def findBadPeakFits(peaksTable, threshold=10): """ Find peaks whose fit values fall outside of a given tolerance of the mean peak centers across all tubes. Tubes are defined as have a bad fit if the absolute difference between the fitted peak centers for a specific tube and the mean of the fitted peak centers for all tubes differ more than the threshold parameter. @param peakTable: the table containing fitted peak centers @param threshold: the tolerance on the difference from the mean value @return A list of expected peak positions and a list of indices of tubes to correct """ n = len(peaksTable) num_peaks = peaksTable.columnCount() - 1 column_names = ['Peak%d' % i for i in range(1, num_peaks + 1)] data = np.zeros((n, num_peaks)) for i, row in enumerate(peaksTable): data_row = [row[name] for name in column_names] data[i, :] = data_row # data now has all the peaks positions for each tube # the mean value is the expected value for the peak position for each tube expected_peak_pos = np.mean(data, axis=0) # calculate how far from the expected position each peak position is distance_from_expected = np.abs(data - expected_peak_pos) check = np.where(distance_from_expected > threshold)[0] problematic_tubes = list(set(check)) print("Problematic tubes are: " + str(problematic_tubes)) return expected_peak_pos, problematic_tubes def correctMisalignedTubes(ws, calibrationTable, peaksTable, spec, idealTube, fitPar, threshold=10): """ Correct misaligned tubes due to poor fitting results during the first round of calibration. Misaligned tubes are first identified according to a tolerance applied to the absolute difference between the fitted tube positions and the mean across all tubes. The FindPeaks algorithm is then used to find a better fit with the ideal tube positions as starting parameters for the peak centers. From the refitted peaks the positions of the detectors in the tube are recalculated. @param ws: the workspace to get the tube geometry from @param calibrationTable: the calibration table output from running calibration @param peaksTable: the table containing the fitted peak centers from calibration @param spec: the tube spec for the instrument @param idealTube: the ideal tube for the instrument @param fitPar: the fitting parameters for calibration @param threshold: tolerance defining is a peak is outside of the acceptable range @return table of corrected detector positions """ table_name = calibrationTable.name() + 'Corrected' corrections_table = mantid.CreateEmptyTableWorkspace(OutputWorkspace=table_name) corrections_table.addColumn('int', "Detector ID") corrections_table.addColumn('V3D', "Detector Position") mean_peaks, bad_tubes = findBadPeakFits(peaksTable, threshold) for index in bad_tubes: print("Refitting tube %s" % spec.getTubeName(index)) tube_dets, _ = spec.getTube(index) getPoints(ws, idealTube.getFunctionalForms(), fitPar, tube_dets) tube_ws = mantid.mtd['TubePlot'] fit_ws = mantid.FindPeaks(InputWorkspace=tube_ws, WorkspaceIndex=0, PeakPositions=fitPar.getPeaks(), PeaksList='RefittedPeaks') centers = [row['centre'] for row in fit_ws] detIDList, detPosList = getCalibratedPixelPositions(ws, centers, idealTube.getArray(), tube_dets) for id, pos in zip(detIDList, detPosList): corrections_table.addRow({'Detector ID': id, 'Detector Position': kernel.V3D(*pos)}) return corrections_table corrected_calibration_table = correctMisalignedTubes(ws, calibrationTable, peaks, spec, idealTube, fitPar) self.correction_table = corrected_calibration_table tube.saveCalibration(self.correction_table.getName(), out_path=self.calibration_out_path) tube.saveCalibration(self.calibration_table.getName(), out_path=self.correction_out_path)
def calibrateMerlin(filename): # == Set parameters for calibration == rangeLower = 3000 # Integrate counts in each spectra from rangeLower to rangeUpper rangeUpper = 20000 # # Get calibration raw file and integrate it rawCalibInstWS = LoadRaw(filename) #'raw' in 'rawCalibInstWS' means unintegrated. print "Integrating Workspace" CalibInstWS = Integration( rawCalibInstWS, RangeLower=rangeLower, RangeUpper=rangeUpper ) DeleteWorkspace(rawCalibInstWS) print "Created workspace (CalibInstWS) with integrated data from run and instrument to calibrate" # the known positions are given in pixels inside the tubes and transformed to provide the positions # with the center of the tube as the origin knownPositions = 2.92713867188*(numpy.array([ 27.30074322, 92.5, 294.65178585, 362.37861919 , 512.77103043 ,663.41425323, 798.3223896, 930.9, 997.08480835])/1024 - 0.5) funcForm = numpy.array([2,2,1,1,1,1,1,2,2],numpy.int8) # The calibration will follow different steps for sets of tubes # For the door9, the best points to define the known positions are the 1st edge, 5 peaks, last edge. points7 = knownPositions[[0,2,3,4,5,6,8]] points7func = funcForm[[0,2,3,4,5,6,8]] door9pos = points7 door9func = points7func CalibratedComponent = 'MERLIN/door9' # door9 # == Get the calibration and put results into calibration table == # also put peaks into PeakFile calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, door9pos, door9func, outputPeak=True, margin=30, rangeList=range(20) # because 20, 21, 22, 23 are defective detectors ) print "Got calibration (new positions of detectors) and put slit peaks into file TubeDemoMerlin01.txt" analisePeakTable(peakTable, 'door9_tube1_peaks') # For the door8, the best points to define the known positions are the 1st edge, 5 peaks, last_edge door8pos = points7 door8func = points7func CalibratedComponent = 'MERLIN/door8' calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, door8pos, door8func, outputPeak = True, #change to peakTable to append to peakTable calibTable = calibrationTable, margin = 30) analisePeakTable(peakTable, 'door8_peaks') # For the doors 7,6,5,4, 2, 1 we may use the 9 points doorpos = knownPositions doorfunc = funcForm CalibratedComponent = ['MERLIN/door%d'%(i) for i in [7,6,5,4, 2, 1]] calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, doorpos,\ doorfunc,\ outputPeak = True,\ calibTable = calibrationTable,\ margin = 30) analisePeakTable(peakTable, 'door1to7_peaks') # The door 3 is a special case, because it is composed by diffent kind of tubes. # door 3 tubes: 5_8, 5_7, 5_6, 5_5, 5_4, 5_3, 5_2, 5_1, 4_8, 4_7, 4_6, 4_5, 4_4, 4_3, 4_2, 4_1, 3_8, 3_7, 3_6, 3_5, 3_4 # obeys the same rules as the doors 7, 6, 5, 4, 2, 1 # For the tubes 3_3, 3_2, 3_1 -> it is better to skip the central peak # For the tubes 1_x (smaller tube below), it is better to take the final part of known positions: peak4,peak5,edge6,edge7 # For the tubes 2_x (smaller tube above, it is better to take the first part of known positions: edge1, edge2, peak1,peak2 # NOTE: the smaller tubes they have length = 1.22879882813, but 1024 detectors # so we have to correct the known positiosn by multiplying by its lenght and dividing by the longer dimension from tube_calib_fit_params import TubeCalibFitParams # calibrating tubes 1_x CalibratedComponent = ['MERLIN/door3/tube_1_%d'%(i) for i in range(1,9)] half_diff_center = (2.92713867188 -1.22879882813)/2 # difference among the expected center position for both tubes # here a little bit of attempts is necessary. The efective center position and lengh is different for the calibrated tube, that # is the reason, the calibrated values of the smaller tube does not seems aligned with the others. By, finding the 'best' half_diff_center # value, the alignment occurs nicely. half_diff_center = 0.835 # # the knownpositions were given with the center of the bigger tube as origin, to convert # to the center of the upper tube as origin is necessary to subtract them with the half_diff_center doorpos = knownPositions[[5,6,7,8]] - half_diff_center doorfunc = [1,1,2,2] # for the smal tubes, automatically searching for the peak position in pixel was not working quite well, # so we will give the aproximate position for these tubes through fitPar argument fitPar = TubeCalibFitParams([216, 527, 826, 989]) fitPar.setAutomatic(True) calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, doorpos,\ doorfunc,\ outputPeak = True,\ fitPar = fitPar,\ calibTable = calibrationTable,\ margin = 30) analisePeakTable(peakTable, 'door3_tube1_peaks') # calibrating tubes 2_x CalibratedComponent = ['MERLIN/door3/tube_2_%d'%(i) for i in range(1,9)] # the knownpositions were given with the center of the bigger tube as origin, to convert # to the center of the lower tube as origin is necessary to sum them with (len_big - len_small)/2 doorpos = knownPositions[[0,1,2,3]] + half_diff_center # print doorpos doorfunc = [2,2,1,1] # for the smal tubes, automatically searching for the peak position in pixel was not working quite well, # so we will give the aproximate position for these tubes through fitPar argument fitPar = TubeCalibFitParams([50, 202, 664, 815]) fitPar.setAutomatic(True) calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, doorpos,\ doorfunc,\ outputPeak = True,\ calibTable = calibrationTable,\ fitPar = fitPar,\ margin = 30) analisePeakTable(peakTable, 'door3_tube2_peaks') # calibrating tubes 3_3,3_2,3_1 CalibratedComponent = ['MERLIN/door3/tube_3_%d'%(i) for i in [1,2,3]] doorpos = knownPositions[[0,1,2,3,5,6,7,8]] doorfunc = funcForm[[0,1,2,3,5,6,7,8]] calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, doorpos,\ doorfunc,\ outputPeak = True,\ calibTable = calibrationTable,\ margin = 30) analisePeakTable(peakTable, 'door3_123_peaks') # calibrating others inside door3 # 5_8, 5_7, 5_6, 5_5, 5_4, 5_3, 5_2, 5_1, 4_8, 4_7, 4_6, 4_5, 4_4, 4_3, 4_2, 4_1, 3_8, 3_7, 3_6, 3_5, 3_4 part_3 = ['MERLIN/door3/tube_3_%d'%(i) for i in [4,5,6,7,8]] part_4 = ['MERLIN/door3/tube_4_%d'%(i) for i in range(1,9)] part_5 = ['MERLIN/door3/tube_5_%d'%(i) for i in range(1,9)] CalibratedComponent = part_3 + part_4 + part_5 doorpos = knownPositions doorfunc = funcForm calibrationTable, peakTable = tube.calibrate(CalibInstWS, CalibratedComponent, doorpos,\ doorfunc,\ outputPeak = True,\ calibTable = calibrationTable,\ margin = 30) analisePeakTable(peakTable, 'door3_peaks') # == Apply the Calibation == ApplyCalibration( Workspace=CalibInstWS, PositionTable=calibrationTable) print "Applied calibration"
# == Create Objects needed for calibration == # The positions of the shadows and ends here are an intelligent guess. # First array gives positions in Metres and second array gives type 1=Gaussian peak 2=edge. knownPos = [-0.65, -0.22, -0.00, 0.22, 0.65] funcForm = [2, 1, 1, 1, 2] # Get fitting parameters # Set initial parameters for peak finding ExpectedHeight = -1000.0 # Expected Height of Gaussian Peaks (initial value of fit parameter) ExpectedWidth = 8.0 # Expected width of Gaussian peaks in pixels (initial value of fit parameter) ExpectedPositions = [4.0, 85.0, 128.0, 161.0, 252.0] # Expected positions of the edges and Gaussian peaks # in pixels (initial values of fit parameters) fitPar = TubeCalibFitParams(ExpectedPositions, ExpectedHeight, ExpectedWidth) fitPar.setAutomatic(True) print("Created objects needed for calibration.") # == Get the calibration and put results into calibration table == calibrationTable = tube.calibrate(CalibInstWS, CalibratedComponent, knownPos, funcForm, fitPar=fitPar) print("Got calibration (new positions of detectors) ") # == Apply the Calibation == mantid.ApplyCalibration(Workspace=CalibInstWS, PositionTable=calibrationTable) print("Applied calibration") # == Save workspace == # mantid.SaveNexusProcessed(CalibInstWS, 'TubeCalibDemoMapsResult.nxs', "Result of Running TCDemoMaps.py")