diff --git a/scripts/Diffraction/isis_powder/gem.py b/scripts/Diffraction/isis_powder/gem.py index 553b1b0e9f4f41ed5754d2e02e0e0f335da49c21..13b7c018e1ea956631e643239b5856096f68f9a2 100644 --- a/scripts/Diffraction/isis_powder/gem.py +++ b/scripts/Diffraction/isis_powder/gem.py @@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) from isis_powder.abstract_inst import AbstractInst from isis_powder.gem_routines import gem_advanced_config, gem_algs, gem_param_mapping -from isis_powder.routines import common, instrument_settings +from isis_powder.routines import common, instrument_settings, sample_details class Gem(AbstractInst): @@ -16,6 +16,9 @@ class Gem(AbstractInst): output_dir=self._inst_settings.output_dir, inst_prefix="GEM") self._cached_run_details = {} + self._sample_details = None + + # Public API def focus(self, **kwargs): self._inst_settings.update_attributes(kwargs=kwargs) @@ -28,6 +31,18 @@ class Gem(AbstractInst): return self._create_vanadium(run_number_string=self._inst_settings.run_in_range, do_absorb_corrections=self._inst_settings.do_absorb_corrections) + def set_sample_details(self, **kwargs): + sample_details_obj = common.dictionary_key_helper( + dictionary=kwargs, key="new_sample_details", + exception_msg="The argument containing sample details was not found. Please" + " set the following argument: new_sample_details") + if not isinstance(sample_details_obj, sample_details.SampleDetails): + raise ValueError("The object passed was not a SampleDetails object. Please create a" + " SampleDetails object and set the relevant properties to use on this method") + self._sample_details = sample_details_obj + + # Private methods + def _get_run_details(self, run_number_string): run_number_string_key = run_number_string + str(self._inst_settings.file_extension) if run_number_string_key in self._cached_run_details: diff --git a/scripts/Diffraction/isis_powder/polaris.py b/scripts/Diffraction/isis_powder/polaris.py index 1f841dd02984d7daf47b279d213ab5c9e5ce0ffb..56bb5fab00300d4daf458cc3e1b7b98563ffd7a9 100644 --- a/scripts/Diffraction/isis_powder/polaris.py +++ b/scripts/Diffraction/isis_powder/polaris.py @@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) import os -from isis_powder.routines import common, instrument_settings +from isis_powder.routines import common, instrument_settings, sample_details from isis_powder.abstract_inst import AbstractInst from isis_powder.polaris_routines import polaris_advanced_config, polaris_algs, polaris_param_mapping @@ -19,6 +19,9 @@ class Polaris(AbstractInst): # Hold the last dictionary later to avoid us having to keep parsing the YAML self._run_details_cached_obj = {} + self._sample_details = None + + # Public API def focus(self, **kwargs): self._inst_settings.update_attributes(kwargs=kwargs) @@ -30,6 +33,16 @@ class Polaris(AbstractInst): return self._create_vanadium(run_number_string=self._inst_settings.run_in_range, do_absorb_corrections=self._inst_settings.do_absorb_corrections) + def set_sample_details(self, **kwargs): + sample_details_obj = common.dictionary_key_helper( + dictionary=kwargs, key="new_sample_details", + exception_msg="The argument containing sample details was not found. Please" + " set the following argument: new_sample_details") + if not isinstance(sample_details_obj, sample_details.SampleDetails): + raise ValueError("The object passed was not a SampleDetails object. Please create a" + " SampleDetails object and set the relevant properties to use on this method") + self._sample_details = sample_details_obj + # Overrides def _apply_absorb_corrections(self, run_details, van_ws): return polaris_algs.calculate_absorb_corrections(ws_to_correct=van_ws, diff --git a/scripts/Diffraction/isis_powder/routines/absorb_corrections.py b/scripts/Diffraction/isis_powder/routines/absorb_corrections.py index 9544d17a12c3ed3beaad6560d21d62cfbadbf397..dfef2920da4aa9ecc13250337024bb69cadadcb1 100644 --- a/scripts/Diffraction/isis_powder/routines/absorb_corrections.py +++ b/scripts/Diffraction/isis_powder/routines/absorb_corrections.py @@ -2,22 +2,15 @@ from __future__ import (absolute_import, division, print_function) import mantid.simpleapi as mantid -from isis_powder.routines import common +from isis_powder.routines import common, sample_details -def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, config_dict): +def create_vanadium_sample_details_obj(config_dict): """ - Sets a cylindrical sample from the user specified config dictionary and performs Mayers - sample correction on the workspace. The config dictionary must be for a cylinder and contain - the keys "cylinder_sample_height", "cylinder_sample_radius", "cylinder_position" and - "chemical_formula". If any of these keys are not found an exception is raise informing - the user that the key was not in the advanced configuration file. - Additionally it checks the value of multiple_scattering to determine whether to take - into account the effects of multiple scattering. - :param ws_to_correct: The workspace to perform Mayers sample correction on - :param multiple_scattering: Boolean of whether to account for the effects of multiple scattering - :param config_dict: A dictionary containing the required keys to set a cylinder sample - :return: The corrected workspace + Creates a SampleDetails object based on a vanadium sample which is found + in the advanced config of an instrument. + :param config_dict: The advanced config dictionary of the instrument for the vanadium sample + :return: A sample details object which holds properties used in sample corrections """ height_key = "cylinder_sample_height" radius_key = "cylinder_sample_radius" @@ -30,18 +23,37 @@ def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, config_d height = common.dictionary_key_helper(dictionary=config_dict, key=height_key, exception_msg=e_msg + height_key) radius = common.dictionary_key_helper(dictionary=config_dict, key=radius_key, exception_msg=e_msg + radius_key) pos = common.dictionary_key_helper(dictionary=config_dict, key=pos_key, exception_msg=e_msg + pos_key) - formula = common.dictionary_key_helper(dictionary=config_dict, key=formula_key, exception_msg=e_msg + formula_key) - if len(formula) > 1: - # Not a trivial element so we need them to manually specify number density as Mantid - # struggles to calculate it - number_density = common.dictionary_key_helper( - dictionary=config_dict, key=number_density_key, - exception_msg="The number density is required as the chemical formula (" + str(formula) + ")" - " is not a single element. The number density was not found. Please add the following key: " + - number_density_key) - else: - number_density = None + number_density = common.dictionary_key_helper(dictionary=config_dict, key=number_density_key, throws=False) + + vanadium_sample_details = sample_details.SampleDetails(height=height, radius=radius, pos=pos) + vanadium_sample_details.set_material(chemical_formula=formula, number_density=number_density) + return vanadium_sample_details + + +def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, sample_details_obj): + """ + Sets a cylindrical sample from the user specified config dictionary and performs Mayers + sample correction on the workspace. The SampleDetails object defines the sample, material + and associated properties. Additionally it checks the value of multiple_scattering to + determine whether to take into account the effects of multiple scattering. + :param ws_to_correct: The workspace to perform Mayers sample correction on + :param multiple_scattering: Boolean of whether to account for the effects of multiple scattering + :param sample_details_obj: The object containing the sample details + :return: The corrected workspace + """ + + height = sample_details_obj.height + radius = sample_details_obj.radius + pos = sample_details_obj.center + + # Get the underlying material object + if not sample_details_obj.is_material_set(): + raise RuntimeError("The material for this sample has not been set yet. Please call" + " set_material on the SampleDetails object to set the material") + material_obj = sample_details_obj.material_object + formula = material_obj.chemical_formula + number_density = material_obj.number_density ws_to_correct = _calculate__cylinder_absorb_corrections( ws_to_correct=ws_to_correct, multiple_scattering=multiple_scattering, diff --git a/scripts/Diffraction/isis_powder/routines/sample_details.py b/scripts/Diffraction/isis_powder/routines/sample_details.py index 041d4da5a839bff6dd32c3ffa91e8af99dd85381..3247e3e7286bd0fb61d5845e13b4f11b7b9c8d08 100644 --- a/scripts/Diffraction/isis_powder/routines/sample_details.py +++ b/scripts/Diffraction/isis_powder/routines/sample_details.py @@ -20,13 +20,16 @@ class SampleDetails(object): self.radius = float(radius) self.center = [float(i) for i in center] # List of X, Y, Z position - self._material_object = None + self.material_object = None + + def is_material_set(self): + return self.material_object is not None def print_sample_details(self): self._print() def reset_sample_material(self): - self._material_object = None + self.material_object = None def set_material(self, **kwargs): chemical_formula = common.dictionary_key_helper(dictionary=kwargs, key="chemical_formula", @@ -34,13 +37,13 @@ class SampleDetails(object): " passed: chemical_formula") number_density = common.dictionary_key_helper(dictionary=kwargs, key="number_density", throws=False) - if self._material_object is not None: + if self.material_object is not None: self.print_sample_details() raise RuntimeError("The material has already been set to the above details. If the properties" " have not been set they can be modified with 'set_material_properties()'. Otherwise" " to change the material call 'reset_sample_material()'") - self._material_object = _Material(chemical_formula=chemical_formula, numeric_density=number_density) + self.material_object = _Material(chemical_formula=chemical_formula, numeric_density=number_density) def set_material_properties(self, **kwargs): err_msg = "The following argument is required but was not set or passed: " @@ -48,12 +51,12 @@ class SampleDetails(object): exception_msg=err_msg + "absorption_cross_section") scattering_cross_section = common.dictionary_key_helper(dictionary=kwargs, key="scattering_cross_section", exception_msg=err_msg + "scattering_cross_section") - if self._material_object is None: + if self.material_object is None: raise RuntimeError("The material has not been set (or reset). Please set it by calling" " 'set_material()' to set the material details of the sample.") - self._material_object.set_material_properties(abs_cross_sect=absorption_cross_section, - scattering_cross_sect=scattering_cross_section) + self.material_object.set_material_properties(abs_cross_sect=absorption_cross_section, + scattering_cross_sect=scattering_cross_section) def _print(self): print("Sample Details:") @@ -63,10 +66,10 @@ class SampleDetails(object): print("Radius: {}".format(self.radius)) print("Center X:{}, Y:{}, Z{}".format(self.center[0], self.center[1], self.center[2])) print("------------------------") - if self._material_object is None: + if self.material_object is None: print("Material has not been set (or has been reset).") else: - self._material_object.print_material() + self.material_object.print_material() print() # Newline for visual spacing @staticmethod @@ -93,7 +96,7 @@ class SampleDetails(object): class _Material(object): def __init__(self, chemical_formula, numeric_density=None): - self._chemical_formula = chemical_formula + self.chemical_formula = chemical_formula # If it is not an element Mantid requires us to provide the numeric density # which is required for absorption corrections. @@ -105,11 +108,11 @@ class _Material(object): # Always check value is sane if user has given one _check_value_is_physical(property_name="numeric_density", value=numeric_density) - self._numeric_density = numeric_density + self.numeric_density = numeric_density # Advanced material properties - self._absorption_cross_section = None - self._scattering_cross_section = None + self.absorption_cross_section = None + self.scattering_cross_section = None # Internal flags so we are only allowed to set the material properties once self._is_material_props_set = False @@ -117,18 +120,18 @@ class _Material(object): def print_material(self): print("Material properties:") print("------------------------") - print("Chemical formula: {}".format(self._chemical_formula)) + print("Chemical formula: {}".format(self.chemical_formula)) - if self._numeric_density: - print("Numeric Density: {}".format(self._numeric_density)) + if self.numeric_density: + print("Numeric Density: {}".format(self.numeric_density)) else: print("Numeric Density: Set from elemental properties by Mantid") self._print_material_properties() def _print_material_properties(self): if self._is_material_props_set: - print("Absorption cross section: {}".format(self._absorption_cross_section)) - print("Scattering cross section: {}".format(self._scattering_cross_section)) + print("Absorption cross section: {}".format(self.absorption_cross_section)) + print("Scattering cross section: {}".format(self.scattering_cross_section)) else: print("Absorption cross section: Calculated by Mantid based on chemical/elemental formula") print("Scattering cross section: Calculated by Mantid based on chemical/elemental formula") @@ -143,8 +146,8 @@ class _Material(object): _check_value_is_physical("absorption_cross_section", abs_cross_sect) _check_value_is_physical("scattering_cross_section", scattering_cross_sect) - self._absorption_cross_section = float(abs_cross_sect) - self._scattering_cross_section = float(scattering_cross_sect) + self.absorption_cross_section = float(abs_cross_sect) + self.scattering_cross_section = float(scattering_cross_sect) self._is_material_props_set = True diff --git a/scripts/test/ISISPowderSampleDetailsTest.py b/scripts/test/ISISPowderSampleDetailsTest.py index 20fb2e828bee6f7d3734e1745f5dd4a02a923ba6..c997f14795fe100bb638579c7df960236af95f85 100644 --- a/scripts/test/ISISPowderSampleDetailsTest.py +++ b/scripts/test/ISISPowderSampleDetailsTest.py @@ -112,7 +112,7 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): # Check that we can only set a material once. We will test the underlying class elsewhere sample_details_obj.set_material(chemical_formula='V') - self.assertIsNotNone(sample_details_obj._material_object) + self.assertIsNotNone(sample_details_obj.material_object) # Check that the material is now immutable with assertRaisesRegex(self, RuntimeError, "The material has already been set to the above details"): @@ -120,16 +120,16 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): # Check resetting it works sample_details_obj.reset_sample_material() - self.assertIsNone(sample_details_obj._material_object) + self.assertIsNone(sample_details_obj.material_object) # And ensure setting it for a second time works sample_details_obj.set_material(chemical_formula='V') - self.assertIsNotNone(sample_details_obj._material_object) + self.assertIsNotNone(sample_details_obj.material_object) def test_set_material_properties(self): sample_details_obj = sample_details.SampleDetails(height=1.0, radius=1.0, center=[2, 3, 5]) - self.assertIsNone(sample_details_obj._material_object) + self.assertIsNone(sample_details_obj.material_object) # Check we cannot set a material property without setting the underlying material with assertRaisesRegex(self, RuntimeError, "The material has not been set"): @@ -148,24 +148,24 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): material_obj_one_char = sample_details._Material(chemical_formula=chemical_formula_one_char_element) self.assertIsNotNone(material_obj_one_char) - self.assertEqual(material_obj_one_char._chemical_formula, chemical_formula_one_char_element) - self.assertIsNone(material_obj_one_char._numeric_density) + self.assertEqual(material_obj_one_char.chemical_formula, chemical_formula_one_char_element) + self.assertIsNone(material_obj_one_char.numeric_density) # Also check that the absorption and scattering X sections have not been set - self.assertIsNone(material_obj_one_char._absorption_cross_section) - self.assertIsNone(material_obj_one_char._scattering_cross_section) + self.assertIsNone(material_obj_one_char.absorption_cross_section) + self.assertIsNone(material_obj_one_char.scattering_cross_section) self.assertFalse(material_obj_one_char._is_material_props_set) # Check if it accepts two character elements without numeric density material_obj_two_char = sample_details._Material(chemical_formula=chemical_formula_two_char_element) self.assertIsNotNone(material_obj_two_char) - self.assertEqual(material_obj_two_char._chemical_formula, chemical_formula_two_char_element) - self.assertIsNone(material_obj_two_char._numeric_density) + self.assertEqual(material_obj_two_char.chemical_formula, chemical_formula_two_char_element) + self.assertIsNone(material_obj_two_char.numeric_density) # Check it stores numeric density if passed material_obj_numeric_density = sample_details._Material(chemical_formula=chemical_formula_two_char_element, numeric_density=numeric_density_sample) - self.assertEqual(material_obj_numeric_density._numeric_density, numeric_density_sample) + self.assertEqual(material_obj_numeric_density.numeric_density, numeric_density_sample) # Check that it raises an error if we have a non-elemental formula without numeric density with assertRaisesRegex(self, ValueError, "A numeric density formula must be set on a chemical formula"): @@ -174,8 +174,8 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): # Check it constructs if it is given the numeric density too material_obj_num_complex_formula = sample_details._Material(chemical_formula=chemical_formula_complex, numeric_density=numeric_density_sample) - self.assertEqual(material_obj_num_complex_formula._chemical_formula, chemical_formula_complex) - self.assertEqual(material_obj_num_complex_formula._numeric_density, numeric_density_sample) + self.assertEqual(material_obj_num_complex_formula.chemical_formula, chemical_formula_complex) + self.assertEqual(material_obj_num_complex_formula.numeric_density, numeric_density_sample) def test_material_set_properties(self): bad_absorb = '-1' @@ -196,20 +196,20 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=bad_scattering) # Check nothing has been set yet - self.assertIsNone(material_obj._absorption_cross_section) - self.assertIsNone(material_obj._scattering_cross_section) + self.assertIsNone(material_obj.absorption_cross_section) + self.assertIsNone(material_obj.scattering_cross_section) # Set the object this time material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=good_scattering) self.assertTrue(material_obj._is_material_props_set) - self.assertEqual(material_obj._absorption_cross_section, float(good_absorb)) - self.assertEqual(material_obj._scattering_cross_section, float(good_scattering)) + self.assertEqual(material_obj.absorption_cross_section, float(good_absorb)) + self.assertEqual(material_obj.scattering_cross_section, float(good_scattering)) # Check we cannot set it twice and fields do not change with assertRaisesRegex(self, RuntimeError, "The material properties have already been set"): material_obj.set_material_properties(abs_cross_sect=999, scattering_cross_sect=999) - self.assertEqual(material_obj._absorption_cross_section, float(good_absorb)) - self.assertEqual(material_obj._scattering_cross_section, float(good_scattering)) + self.assertEqual(material_obj.absorption_cross_section, float(good_absorb)) + self.assertEqual(material_obj.scattering_cross_section, float(good_scattering)) def test_print_sample_details(self): expected_height = 1