def test_fit_bank(self): with self.assertRaises(AssertionError) as exception_info: fit_bank('I_am_not_here', 'bank42') assert 'Input workspace I_am_not_here does not exists' in str(exception_info.exception) control = self.cases['123455_bank20'] # a bank with a reasonable wire scan with self.assertRaises(AssertionError) as exception_info: fit_bank(control, 'bank20', shadow_height=-1000) assert 'shadow height must be positive' in str(exception_info.exception) with self.assertRaises(AssertionError) as exception_info: fit_bank(control, 'tree/bank51') assert 'The bank name must be of the form' in str(exception_info.exception) with self.assertRaises(AssertionError) as exception_info: fit_bank(self.cases['123555_bank20'], 'bank20') assert 'Insufficient counts per pixel in workspace CORELLI_123555_bank20' in str(exception_info.exception) fit_bank(control, 'bank20') # Expect default name for calibration table assert AnalysisDataService.doesExist('CalibTable') # Expect default name for peak pixel position table assert AnalysisDataService.doesExist('PeakTable') DeleteWorkspaces(['CalibTable', 'PeakTable']) fit_bank(control, 'bank20', calibration_table='table_20', peak_pixel_positions_table='pixel_20') assert AnalysisDataService.doesExist('table_20') assert AnalysisDataService.doesExist('pixel_20') DeleteWorkspaces(['CalibTable', 'PeakTable']) # a bit of clean-up
def test_calibrate_bank(self): # control bank, it has no problems. Thus, no mask to be created calibration, mask = calibrate_bank(self.cases['123455_bank20'], 'bank20', 'calibration_table') assert calibration.rowCount() == 256 * 16 assert calibration.columnCount() == 3 assert AnalysisDataService.doesExist('calibration_table') assert mask is None assert AnalysisDataService.doesExist('MaskTable') is False # tubes 3, 8, and 13 have very faint wire shadows. Thus, mask these tubes calibration, mask = calibrate_bank(self.cases['124023_bank14'], 'bank14', calibration_table='calibration_table') assert calibration.rowCount() == 256 * (16 - 3) assert calibration.columnCount() == 2 # Detector ID, Position assert AnalysisDataService.doesExist('calibration_table') assert mask.rowCount() == 256 * 3 assert mask.columnCount() == 1 assert AnalysisDataService.doesExist('MaskTable') # check for the summary workspace calibrate_bank(self.cases['123455_bank20'], 'bank20', 'calibration_table', criterium_kwargs=dict(summary='summary')) assert AnalysisDataService.doesExist('summary') workspace = mtd['summary'] axis = workspace.getAxis(1) assert [axis.label(workspace_index) for workspace_index in (0, 1, 2)] == ['success', 'deviation', 'Z-score'] self.assertEqual(min(workspace.readY(0)), 1.0) self.assertAlmostEqual(max(workspace.readY(2)), 1.728, delta=0.001) DeleteWorkspaces(['calibration_table', 'MaskTable', 'summary']) # a bit of clean-up
def test_calibrate_bank(self): # control bank, it has no problems. Thus, no mask to be created calibration, mask = calibrate_bank(self.cases['123455_bank20'], 'bank20', 'calibration_table') assert calibration.rowCount() == 256 * 16 assert calibration.columnCount() == 2 assert AnalysisDataService.doesExist('calibration_table') assert mask is None assert AnalysisDataService.doesExist('MaskTable') is False DeleteWorkspaces(['calibration_table']) # clean-up # beam center intensity spills over adjacent tubes, tube15 and tube16 calibration, mask = calibrate_bank(self.cases['123454_bank58'], 'bank58', 'calibration_table') assert calibration.rowCount() == 256 * (16 - 2) assert calibration.columnCount() == 2 # Detector ID, Position assert AnalysisDataService.doesExist('calibration_table') assert mask.rowCount() == 256 * 2 assert mask.columnCount() == 1 assert AnalysisDataService.doesExist('MaskTable') DeleteWorkspaces(['calibration_table', 'MaskTable']) # clean-up # check for the fits workspace calibrate_bank(self.cases['123455_bank20'], 'bank20', 'calibration_table', fit_results='fits') assert AnalysisDataService.doesExist('fits') workspace = mtd['fits'] axis = workspace.getAxis(1) labels = [axis.label(i) for i in range(workspace.getNumberHistograms())] assert labels == ['success', 'deviation', 'Z-score', 'A0', 'A1', 'A2'] assert_allclose(workspace.readY(0), [1.0] * TUBES_IN_BANK) # success status for first tube self.assertAlmostEqual(max(workspace.readY(2)), 2.73, delta=0.01) # maximum Z-score self.assertAlmostEqual(max(workspace.readY(3)), -0.445, delta=0.001) # maximum A0 value self.assertAlmostEqual(max(workspace.readE(3)), 1.251, delta=0.001) # maximum A0 error DeleteWorkspaces(['calibration_table', 'fits']) # a bit of clean-up
def tearDown(self) -> None: to_delete = [ w for w in [self.workspace, self.table] if AnalysisDataService.doesExist(w) ] if len(to_delete) > 0: DeleteWorkspaces(to_delete)
def mask_bank(bank_name: str, tubes_fit_success: np.ndarray, output_table: str) -> Optional[TableWorkspace]: r""" Creates a single-column `TableWorkspace` object containing the detector ID's of the unsuccessfully fitted tubes If all tubes were fit successfully, no `TableWorkspace` is created, and `None` is returned. :param bank_name: a string of the form 'bankI' where 'I' is a bank number :param tubes_fit_success: array of boolean containing a True/False entry for each tube, indicating wether the tube was successfully calibrated. :param output_table: name of the output TableWorkspace containing one column for detector ID from tubes not successfully calibrated. :raise AssertionError: the string bank_name does not follow the pattern 'bankI' where 'I' in an integer :return: name of the mask TableWorkspace. Returns `None` if no TableWorkspace is created. """ assert re.match(r'^bank\d+$', bank_name), 'The bank name must be of the form "bankI" where "I" in an integer' if False not in tubes_fit_success: return None # al tubes were fit successfully bank_number = bank_name[4:] # drop 'bank' from bank_name tube_numbers = 1 + np.where(tubes_fit_success == False)[0] # noqa E712 unsuccessfully fitted tube numbers tube_numbers = ','.join([str(n) for n in tube_numbers]) # failing tubes as a string detector_ids = MaskBTP(Instrument='CORELLI', Bank=bank_number, Tube=tube_numbers) table = CreateEmptyTableWorkspace(OutputWorkspace=output_table) table.addColumn('long64', 'Detector ID') [table.addRow([detector_id]) for detector_id in detector_ids.tolist()] if AnalysisDataService.doesExist('CORELLIMaskBTP'): DeleteWorkspaces(['CORELLIMaskBTP']) return mtd[output_table]
def test_fit_bank(self): control = self.cases['123455_bank20'] # a bank with a reasonable wire scan with self.assertRaises(AssertionError) as exception_info: fit_bank('I_am_not_here', 'bank42') with self.assertRaises(AssertionError) as exception_info: fit_bank(control, 'bank20', shadow_height=-1000) assert 'shadow height must be positive' in str(exception_info.exception) with self.assertRaises(AssertionError) as exception_info: fit_bank(control, 'tree/bank51') assert 'The bank name must be of the form' in str(exception_info.exception) with self.assertRaises(AssertionError) as exception_info: fit_bank(self.cases['123555_bank20'], 'bank20') assert 'Insufficient counts per pixel in workspace CORELLI_123555_bank20' in str(exception_info.exception) fit_bank(control, 'bank20', parameters_table_group='parameters_table') # Expect default name for calibration table assert AnalysisDataService.doesExist('CalibTable') # Expect default name for peak pixel position table assert AnalysisDataService.doesExist('PeakTable') # Expect default name for peak pixel position table assert AnalysisDataService.doesExist('PeakYTable') # assert the parameters tables are created assert AnalysisDataService.doesExist('parameters_table') for tube_number in range(TUBES_IN_BANK): assert AnalysisDataService.doesExist(f'parameters_table_{tube_number}') DeleteWorkspaces(['CalibTable', 'PeakTable', 'PeakYTable', 'parameters_table']) fit_bank(control, 'bank20', calibration_table='table_20', peak_pixel_positions_table='pixel_20') assert AnalysisDataService.doesExist('table_20') assert AnalysisDataService.doesExist('pixel_20') DeleteWorkspaces(['table_20', 'pixel_20', 'PeakYTable', 'ParametersTable']) # a bit of clean-up
def test_new_corelli_calibration_and_load_calibration(self): r"""Creating a database is time consuming, thus we test both new_corelli_calibration and load_calibration""" # populate a calibration database with a few cases. There should be at least one bank with two calibrations database = tempfile.TemporaryDirectory() cases = [('124016_bank10', '10'), ('124023_bank10', '10'), ('124023_banks_14_15', '14-15')] for bank_case, bank_selection in cases: # Produce workspace groups 'calibrations', 'masks', 'fits' calibrate_banks(self.cases[bank_case], bank_selection) masks = 'masks' if AnalysisDataService.doesExist('masks') else None save_calibration_set(self.cases[bank_case], database.name, 'calibrations', masks, 'fits') DeleteWorkspaces(['calibrations', 'fits']) if AnalysisDataService.doesExist('masks'): DeleteWorkspaces(['masks']) # invoque creation of new corelli calibration without a date calibration_file, mask_file, manifest_file = new_corelli_calibration(database.name) for file_path in (calibration_file, mask_file, manifest_file): assert path.exists(file_path) assert open(manifest_file).read() == 'bankID, timestamp\n10, 20200109\n14, 20200109\n15, 20200109\n' # load latest calibration and mask (day-stamp of '124023_bank10' is 20200109) calibration, mask = load_calibration_set(self.cases['124023_bank10'], database.name, mask_format='TableWorkspace') calibration_expected = LoadNexusProcessed(Filename=calibration_file) mask_expected = LoadNexusProcessed(Filename=mask_file) assert_allclose(calibration.column(1), calibration_expected.column(1), atol=1e-4) assert mask.column(0) == mask_expected.column(0) # invoque a new corelli calibration with a date falling in between the bank (bank10) in # in our small dataset having two calibrations calibration_file, mask_file, manifest_file = new_corelli_calibration(database.name, date='20200108') for file_path in (calibration_file, mask_file, manifest_file): assert path.exists(file_path) assert open(manifest_file).read() == 'bankID, timestamp\n10, 20200106\n' # load oldest calibration and mask(day-stamp of '124023_bank10' is 20200106) calibration, mask = load_calibration_set(self.cases['124016_bank10'], database.name, mask_format='TableWorkspace') calibration_expected = LoadNexusProcessed(Filename=calibration_file) mask_expected = LoadNexusProcessed(Filename=mask_file) assert_allclose(calibration.column(1), calibration_expected.column(1), atol=1e-4) assert mask.column(0) == mask_expected.column(0) database.cleanup()
def purge_table(workspace: WorkspaceTypes, calibration_table: TableWorkspace, tubes_fit_success: np.ndarray, output_table: str = None) -> None: r""" Remove the detectorID's corresponding to the failing tubes from the calibration table Assumptions: - Each tube has PIXELS_PER_TUBE number of pixels - detector ID's in the calibration table are sorted according to tube number :param workspace: input Workspace2D containing total neutron counts per pixel :param calibration_table: input TableWorkspace containing one column for detector ID and one column for its calibrated Y coordinates, in meters :param tubes_fit_success: array of booleans of length TUBES_IN_BANK. `False` if a tube was unsuccessfully fitted. :param output_table: name of the purged table. If `None`, the input `calibration_table` is purged. """ # validate input arguments if False not in tubes_fit_success: return # nothing to do # validate the input workspace message = f'Cannot process workspace {workspace}. Pass the name of an existing workspace or a workspace handle' assert isinstance(workspace, (str, Workspace2D)), message workspace_name = str(workspace) assert AnalysisDataService.doesExist(workspace_name), f'Input workspace {workspace_name} does not exists' # validate the input calibraton table message = f'Cannot process table {calibration_table}. Pass the name of an existing TableWorkspace' \ ' or a TableWorkspace handle' assert isinstance(calibration_table, (str, TableWorkspace)), message assert AnalysisDataService.doesExist(str(calibration_table)), f'Input table {calibration_table} does not exists' if output_table is not None: CloneWorkspace(InputWorkspace=calibration_table, OutputWorkspace=output_table) else: output_table = str(calibration_table) tube_fail_indexes = np.where(tubes_fit_success == False)[0] # noqa E712 indexes of tubes unsuccessfully fitted row_indexes_of_first_tube = np.arange(PIXELS_PER_TUBE) # 0, 1, ... 255 fail_rows = [row_indexes_of_first_tube + (i * PIXELS_PER_TUBE) for i in tube_fail_indexes] fail_rows = np.array(fail_rows, dtype=int).flatten().tolist() DeleteTableRows(output_table, fail_rows)
def test_save_calibration_set(self) -> None: calibrations, masks = calibrate_banks(self.cases['124023_banks_14_15'], '14-15') for w in ('calibrations', 'masks', 'fits'): assert AnalysisDataService.doesExist(w) # Save everything (typical case) database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, 'calibrations', 'masks', 'fits') for bn in ('014', '015'): # bank number for ct in ('calibration', 'mask', 'fit'): # table type assert path.exists(path.join(database.name, f'bank{bn}', f'{ct}_corelli_bank{bn}_20200109.nxs.h5')) database.cleanup() # Save only the calibration tables database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, 'calibrations') for bn in ('014', '015'): # bank number assert path.exists(path.join(database.name, f'bank{bn}', f'calibration_corelli_bank{bn}_20200109.nxs.h5')) database.cleanup() # Save only the calibration tables as a list of strings database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, ['calib14', 'calib15']) for bn in ('014', '015'): # bank number assert path.exists(path.join(database.name, f'bank{bn}', f'calibration_corelli_bank{bn}_20200109.nxs.h5')) database.cleanup() # Save only the calibration tables as a list of workspaces database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, [mtd['calib14'], mtd['calib15']]) for bn in ('014', '015'): # bank number assert path.exists(path.join(database.name, f'bank{bn}', f'calibration_corelli_bank{bn}_20200109.nxs.h5')) database.cleanup() # Save only one table of each type, passing strings database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, 'calib14', 'mask14', 'fit14') for ct in ('calibration', 'mask', 'fit'): # table type assert path.exists(path.join(database.name, 'bank014', f'{ct}_corelli_bank014_20200109.nxs.h5')) database.cleanup() # Save only one table of each type, passing workspaces database = tempfile.TemporaryDirectory() save_calibration_set(self.cases['124023_banks_14_15'], database.name, mtd['calib14'], mtd['mask14'], mtd['fit14']) for ct in ('calibration', 'mask', 'fit'): # table type assert path.exists(path.join(database.name, 'bank014', f'{ct}_corelli_bank014_20200109.nxs.h5')) database.cleanup()
def test_criterion_peak_vertical_position(self): # control bank, it has no problems fit_bank(self.cases['123455_bank20'], 'bank20') expected = np.ones(16, dtype=bool) actual = criterion_peak_vertical_position('PeakYTable', zscore_threshold=2.5, deviation_threshold=0.0035) assert_equal(actual, expected) DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable']) # a bit of clean-up # beam center intensity spills over adjacent tubes, tube15 and tube16 fit_bank(self.cases['123454_bank58'], 'bank58') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], dtype=bool) actual = criterion_peak_vertical_position('PeakYTable', zscore_threshold=2.5, deviation_threshold=0.0035) assert_equal(actual, expected) DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable']) # a bit of clean-up # tube11 is not working at all fit_bank(self.cases['124018_bank45'], 'bank45') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], dtype=bool) actual = criterion_peak_vertical_position('PeakYTable', zscore_threshold=2.5, deviation_threshold=0.0035) assert_equal(actual, expected) DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable']) # a bit of clean-up # tube 13 has shadows at pixel numbers quite different from the rest, but similar vertical positions fit_bank(self.cases['124023_bank10'], 'bank10') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=bool) actual = criterion_peak_vertical_position('PeakYTable', zscore_threshold=2.5, deviation_threshold=0.0035) assert_equal(actual, expected) DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable']) # a bit of clean-up # one spurious shadow in tube14 throws away the fit fit_bank(self.cases['124023_bank15'], 'bank15') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], dtype=bool) actual = criterion_peak_vertical_position('PeakYTable', zscore_threshold=2.5, deviation_threshold=0.0035) assert_equal(actual, expected) DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable']) # a bit of clean-up # check for the summary workspace fit_bank(self.cases['123455_bank20'], 'bank20') criterion_peak_vertical_position('PeakYTable', summary='summary', zscore_threshold=2.5, deviation_threshold=0.0035) assert AnalysisDataService.doesExist('summary') workspace = mtd['summary'] axis = workspace.getAxis(1) assert [axis.label(workspace_index) for workspace_index in (0, 1, 2)] == ['success', 'deviation', 'Z-score'] self.assertEqual(min(workspace.readY(0)), 1.0) # check success of first tube self.assertAlmostEqual(max(workspace.readY(2)), 2.73, delta=0.01) # check maximum Z-score DeleteWorkspaces(['CalibTable', 'ParametersTable', 'PeakTable', 'PeakYTable', 'summary']) # a bit of clean-up
def test_criterium_peak_pixel_position(self): # control bank, it has no problems fit_bank(self.cases['123455_bank20'], 'bank20') expected = np.ones(16, dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # beam center intensity spills over adjacent tubes, tube15 and tube16 fit_bank(self.cases['123454_bank58'], 'bank58') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # tube11 is not working at all fit_bank(self.cases['124018_bank45'], 'bank45') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # tube 13 has shadows at pixel numbers quite different from the rest fit_bank(self.cases['124023_bank10'], 'bank10') expected = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # tubes 3, 8, and 13 have very faint wire shadows fit_bank(self.cases['124023_bank14'], 'bank14') expected = np.array([1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # one spurious shadow in tube14, not enough to flag a discrepancy fit_bank(self.cases['124023_bank15'], 'bank15') expected = np.ones(16, dtype=bool) assert_equal(criterium_peak_pixel_position('PeakTable', zscore_threshold=2.5, deviation_threshold=3), expected) # check for the summary workspace fit_bank(self.cases['123455_bank20'], 'bank20') criterium_peak_pixel_position('PeakTable', summary='summary', zscore_threshold=2.5, deviation_threshold=3) assert AnalysisDataService.doesExist('summary') workspace = mtd['summary'] axis = workspace.getAxis(1) assert [axis.label(workspace_index) for workspace_index in (0, 1, 2)] == ['success', 'deviation', 'Z-score'] self.assertEqual(min(workspace.readY(0)), 1.0) self.assertAlmostEqual(max(workspace.readY(2)), 1.728, delta=0.001) DeleteWorkspaces(['CalibTable', 'PeakTable', 'summary']) # a bit of clean-up
def test_apply_calibration(self) -> None: table = calibrate_tube(self.workspace, 'bank42/sixteenpack/tube8') apply_calibration(self.workspace, table) assert AnalysisDataService.doesExist('uncalibrated_calibrated') DeleteWorkspaces(['uncalibrated_calibrated', str(table)])
def test_peak_y_table(self) -> None: # Mock PeakTable with two tubes and three peaks. Simple, integer values def peak_pixels_table(table_name, peak_count, tube_names=None, pixel_positions=None): table = CreateEmptyTableWorkspace(OutputWorkspace=table_name) table.addColumn(type='str', name='TubeId') for i in range(peak_count): table.addColumn(type='float', name='Peak%d' % (i + 1)) if tube_names is not None and pixel_positions is not None: assert len(tube_names) == len( pixel_positions ), 'tube_names and pixel_positions have different length' for tube_index in range(len(tube_names)): # tube_names is a list of str values; pixel_positions is a list of lists of float values table.addRow([tube_names[tube_index]] + pixel_positions[tube_index]) return table # Create a peak table with only one tube peak_table = peak_pixels_table('PeakTable', 3, ['tube1'], [[0, 1, 2]]) # Mock ParametersTableGroup with one parameter table. Simple parabola def parameters_optimized_table(table_name, values=None, errors=None): table = CreateEmptyTableWorkspace(OutputWorkspace=table_name) for column_type, column_name in [('str', 'Name'), ('float', 'Value'), ('float', 'Error')]: table.addColumn(type=column_type, name=column_name) if values is not None and errors is not None: assert len(values) == 4 and len( errors) == 4 # A0, A1, A2, 'Cost function value' for index, row_name in enumerate( ['A0', 'A1', 'A2', 'Cost function value']): table.addRow([row_name, values[index], errors[index]]) return table # Create two tables with optimized polynomial coefficients, then group them parameters_optimized_table('parameters_table_0', [0, 0, 1, 1.3], [0, 0, 0, 0]) # first tube parameters_optimized_table('parameters_table_1', [1, 1, 1, 2.3], [0, 0, 0, 0]) # second tube parameters_table = GroupWorkspaces( InputWorkspaces=['parameters_table_0', 'parameters_table_1'], OutputWorkspace='parameters_table') # Check we raise an assertion error since the number of tubes is different than number of tables with self.assertRaises(AssertionError) as exception_info: calculate_peak_y_table(peak_table, parameters_table, output_workspace='PeakYTable') assert 'number of rows in peak_table different than' in str( exception_info.exception) # Add another parameter table to ParametersTableGroup, the create peak_vertical_table peak_table.addRow(['tube2', 0, 1, 2]) table = calculate_peak_y_table(peak_table, parameters_table, output_workspace='PeakYTable') assert AnalysisDataService.doesExist('PeakYTable') assert_allclose(list(table.row(0).values())[1:], [0, 1, 4]) assert_allclose(list(table.row(1).values())[1:], [1, 3, 7])
def fit_bank(workspace: WorkspaceTypes, bank_name: str, shadow_height: float = 1000, shadow_width: float = 4, fit_domain: float = 7, minimum_intensity: float = 1000, calibration_table: str = 'CalibTable', peak_pixel_positions_table: str = 'PeakTable', peak_vertical_positions_table: str = 'PeakYTable', parameters_table_group: str = 'ParametersTable') -> None: r""" Find the position of the wire shadow on each tube in the bank, in units of pixel positions :param workspace: input Workspace2D containing total neutron counts per pixel :param bank_name: a string of the form 'bankI' where 'I' is a bank number :param shadow_height: initial guess for the decrease in neutron counts caused by the calibrating wire :param shadow_width: initial guess for the number of pixels shadowed by the calibrating wire :param fit_domain: number of pixels over which a calibrating wire may have any significant influence :param minimum_intensity: mininum number of neutron counts per pixel to warrant a significant fit session. This number is compared against the neutron counts per pixel, averaged over all pixels in the bank :param calibration_table: output TableWorkspace containing one column for detector ID and one column for its calibrated XYZ coordinates, in meters :param peak_pixel_positions_table: output table containing the positions of the wire shadows for each tube, in units of pixel coordinates :param peak_vertical_positions_table: output table containing the positions of the wire shadows for each tube along the vertical (Y) axis. This positions are the result if evaluating the peak pixel positions with the quadratic function that translates pixel positions to vertical positions (see parameters_table_group) :param parameters_table_group: name of the WorkspaceGroup containing individual TableWorkspace tables. Each table holds values and errors for the optimized coefficients of the polynomial that fits the peak positions (in pixel coordinates) to the known wire positions (along the Y-coordinate). The last entry in the table holds the goodness-of-fit, chi-square value. The name of each individual TableWorkspace is the string `parameters_table_group` plus the suffix `_I`, where `I` is the tube index in the bank, startin at zero. If set to `None`, no group workspace is generated. :raises AssertionError: the input workspace is of incorrect type or cannot be found :raises AssertionError: the input `shadow_height` is a non-positive value :raises AssertionError: the `bank_name` if not of the form 'bankI' :raises AssertionError: insufficient neutron counts per pixel :return: workspace handles to the calibration and peak table """ message = f'Cannot process workspace {workspace}. Pass the name of an existing workspace or a workspace handle' assert isinstance(workspace, (str, Workspace2D)), message workspace_name = str(workspace) assert AnalysisDataService.doesExist(workspace_name), f'Input workspace {workspace_name} does not exists' assert shadow_height > 0, 'shadow height must be positive' peak_height, peak_width = -shadow_height, shadow_width assert re.match(r'^bank\d+$', bank_name), 'The bank name must be of the form "bankI" where "I" in an integer' message = f'Insufficient counts per pixel in workspace {workspace_name} for a confident calibration' assert sufficient_intensity(workspace, bank_name, minimum_intensity=minimum_intensity), message # Fit only the inner 14 dips because the extrema wires are too close to the tube tips. # The dead zone in the tube tips interferes with the shadow cast by the extrema wires # preventing a good fitting wire_positions_pixels = wire_positions(units='pixels')[1: -1] wire_count = len(wire_positions_pixels) peaks_form = [1] * wire_count # signals we'll be fitting dips (peaks with negative heights) fit_par = TubeCalibFitParams(wire_positions_pixels, height=peak_height, width=peak_width, margin=fit_domain) fit_par.setAutomatic(True) tube.calibrate(workspace_name, bank_name, wire_positions(units='meters')[1: -1], peaks_form, fitPar=fit_par, outputPeak=True, parameters_table_group=parameters_table_group) if calibration_table != 'CalibTable': RenameWorkspace(InputWorkspace='CalibTable', OutputWorkspace=calibration_table) trim_calibration_table(calibration_table) # discard X and Z coordinates if peak_pixel_positions_table != 'PeakTable': RenameWorkspace(InputWorkspace='PeakTable', OutputWorkspace=peak_pixel_positions_table) calculate_peak_y_table(peak_pixel_positions_table, parameters_table_group, output_workspace=peak_vertical_positions_table)