Skip to content
Snippets Groups Projects
Commit 0fac1e10 authored by David Fairbrother's avatar David Fairbrother
Browse files

Re #19509 Implemented SampleDetails for inst and absorption methods

parent 1bd161dc
No related branches found
No related tags found
No related merge requests found
...@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) ...@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
from isis_powder.abstract_inst import AbstractInst from isis_powder.abstract_inst import AbstractInst
from isis_powder.gem_routines import gem_advanced_config, gem_algs, gem_param_mapping 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): class Gem(AbstractInst):
...@@ -16,6 +16,9 @@ class Gem(AbstractInst): ...@@ -16,6 +16,9 @@ class Gem(AbstractInst):
output_dir=self._inst_settings.output_dir, inst_prefix="GEM") output_dir=self._inst_settings.output_dir, inst_prefix="GEM")
self._cached_run_details = {} self._cached_run_details = {}
self._sample_details = None
# Public API
def focus(self, **kwargs): def focus(self, **kwargs):
self._inst_settings.update_attributes(kwargs=kwargs) self._inst_settings.update_attributes(kwargs=kwargs)
...@@ -28,6 +31,18 @@ class Gem(AbstractInst): ...@@ -28,6 +31,18 @@ class Gem(AbstractInst):
return self._create_vanadium(run_number_string=self._inst_settings.run_in_range, return self._create_vanadium(run_number_string=self._inst_settings.run_in_range,
do_absorb_corrections=self._inst_settings.do_absorb_corrections) 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): def _get_run_details(self, run_number_string):
run_number_string_key = run_number_string + str(self._inst_settings.file_extension) run_number_string_key = run_number_string + str(self._inst_settings.file_extension)
if run_number_string_key in self._cached_run_details: if run_number_string_key in self._cached_run_details:
......
...@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) ...@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
import os 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.abstract_inst import AbstractInst
from isis_powder.polaris_routines import polaris_advanced_config, polaris_algs, polaris_param_mapping from isis_powder.polaris_routines import polaris_advanced_config, polaris_algs, polaris_param_mapping
...@@ -19,6 +19,9 @@ class Polaris(AbstractInst): ...@@ -19,6 +19,9 @@ class Polaris(AbstractInst):
# Hold the last dictionary later to avoid us having to keep parsing the YAML # Hold the last dictionary later to avoid us having to keep parsing the YAML
self._run_details_cached_obj = {} self._run_details_cached_obj = {}
self._sample_details = None
# Public API
def focus(self, **kwargs): def focus(self, **kwargs):
self._inst_settings.update_attributes(kwargs=kwargs) self._inst_settings.update_attributes(kwargs=kwargs)
...@@ -30,6 +33,16 @@ class Polaris(AbstractInst): ...@@ -30,6 +33,16 @@ class Polaris(AbstractInst):
return self._create_vanadium(run_number_string=self._inst_settings.run_in_range, return self._create_vanadium(run_number_string=self._inst_settings.run_in_range,
do_absorb_corrections=self._inst_settings.do_absorb_corrections) 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 # Overrides
def _apply_absorb_corrections(self, run_details, van_ws): def _apply_absorb_corrections(self, run_details, van_ws):
return polaris_algs.calculate_absorb_corrections(ws_to_correct=van_ws, return polaris_algs.calculate_absorb_corrections(ws_to_correct=van_ws,
......
...@@ -2,22 +2,15 @@ from __future__ import (absolute_import, division, print_function) ...@@ -2,22 +2,15 @@ from __future__ import (absolute_import, division, print_function)
import mantid.simpleapi as mantid 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 Creates a SampleDetails object based on a vanadium sample which is found
sample correction on the workspace. The config dictionary must be for a cylinder and contain in the advanced config of an instrument.
the keys "cylinder_sample_height", "cylinder_sample_radius", "cylinder_position" and :param config_dict: The advanced config dictionary of the instrument for the vanadium sample
"chemical_formula". If any of these keys are not found an exception is raise informing :return: A sample details object which holds properties used in sample corrections
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
""" """
height_key = "cylinder_sample_height" height_key = "cylinder_sample_height"
radius_key = "cylinder_sample_radius" radius_key = "cylinder_sample_radius"
...@@ -30,18 +23,37 @@ def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, config_d ...@@ -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) 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) 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) 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) formula = common.dictionary_key_helper(dictionary=config_dict, key=formula_key, exception_msg=e_msg + formula_key)
if len(formula) > 1: number_density = common.dictionary_key_helper(dictionary=config_dict, key=number_density_key, throws=False)
# Not a trivial element so we need them to manually specify number density as Mantid
# struggles to calculate it vanadium_sample_details = sample_details.SampleDetails(height=height, radius=radius, pos=pos)
number_density = common.dictionary_key_helper( vanadium_sample_details.set_material(chemical_formula=formula, number_density=number_density)
dictionary=config_dict, key=number_density_key, return vanadium_sample_details
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) def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, sample_details_obj):
else: """
number_density = None 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 = _calculate__cylinder_absorb_corrections(
ws_to_correct=ws_to_correct, multiple_scattering=multiple_scattering, ws_to_correct=ws_to_correct, multiple_scattering=multiple_scattering,
......
...@@ -20,13 +20,16 @@ class SampleDetails(object): ...@@ -20,13 +20,16 @@ class SampleDetails(object):
self.radius = float(radius) self.radius = float(radius)
self.center = [float(i) for i in center] # List of X, Y, Z position 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): def print_sample_details(self):
self._print() self._print()
def reset_sample_material(self): def reset_sample_material(self):
self._material_object = None self.material_object = None
def set_material(self, **kwargs): def set_material(self, **kwargs):
chemical_formula = common.dictionary_key_helper(dictionary=kwargs, key="chemical_formula", chemical_formula = common.dictionary_key_helper(dictionary=kwargs, key="chemical_formula",
...@@ -34,13 +37,13 @@ class SampleDetails(object): ...@@ -34,13 +37,13 @@ class SampleDetails(object):
" passed: chemical_formula") " passed: chemical_formula")
number_density = common.dictionary_key_helper(dictionary=kwargs, key="number_density", throws=False) 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() self.print_sample_details()
raise RuntimeError("The material has already been set to the above details. If the properties" 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" " have not been set they can be modified with 'set_material_properties()'. Otherwise"
" to change the material call 'reset_sample_material()'") " 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): def set_material_properties(self, **kwargs):
err_msg = "The following argument is required but was not set or passed: " err_msg = "The following argument is required but was not set or passed: "
...@@ -48,12 +51,12 @@ class SampleDetails(object): ...@@ -48,12 +51,12 @@ class SampleDetails(object):
exception_msg=err_msg + "absorption_cross_section") exception_msg=err_msg + "absorption_cross_section")
scattering_cross_section = common.dictionary_key_helper(dictionary=kwargs, key="scattering_cross_section", scattering_cross_section = common.dictionary_key_helper(dictionary=kwargs, key="scattering_cross_section",
exception_msg=err_msg + "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" 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.") " 'set_material()' to set the material details of the sample.")
self._material_object.set_material_properties(abs_cross_sect=absorption_cross_section, self.material_object.set_material_properties(abs_cross_sect=absorption_cross_section,
scattering_cross_sect=scattering_cross_section) scattering_cross_sect=scattering_cross_section)
def _print(self): def _print(self):
print("Sample Details:") print("Sample Details:")
...@@ -63,10 +66,10 @@ class SampleDetails(object): ...@@ -63,10 +66,10 @@ class SampleDetails(object):
print("Radius: {}".format(self.radius)) print("Radius: {}".format(self.radius))
print("Center X:{}, Y:{}, Z{}".format(self.center[0], self.center[1], self.center[2])) print("Center X:{}, Y:{}, Z{}".format(self.center[0], self.center[1], self.center[2]))
print("------------------------") print("------------------------")
if self._material_object is None: if self.material_object is None:
print("Material has not been set (or has been reset).") print("Material has not been set (or has been reset).")
else: else:
self._material_object.print_material() self.material_object.print_material()
print() # Newline for visual spacing print() # Newline for visual spacing
@staticmethod @staticmethod
...@@ -93,7 +96,7 @@ class SampleDetails(object): ...@@ -93,7 +96,7 @@ class SampleDetails(object):
class _Material(object): class _Material(object):
def __init__(self, chemical_formula, numeric_density=None): 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 # If it is not an element Mantid requires us to provide the numeric density
# which is required for absorption corrections. # which is required for absorption corrections.
...@@ -105,11 +108,11 @@ class _Material(object): ...@@ -105,11 +108,11 @@ class _Material(object):
# Always check value is sane if user has given one # Always check value is sane if user has given one
_check_value_is_physical(property_name="numeric_density", value=numeric_density) _check_value_is_physical(property_name="numeric_density", value=numeric_density)
self._numeric_density = numeric_density self.numeric_density = numeric_density
# Advanced material properties # Advanced material properties
self._absorption_cross_section = None self.absorption_cross_section = None
self._scattering_cross_section = None self.scattering_cross_section = None
# Internal flags so we are only allowed to set the material properties once # Internal flags so we are only allowed to set the material properties once
self._is_material_props_set = False self._is_material_props_set = False
...@@ -117,18 +120,18 @@ class _Material(object): ...@@ -117,18 +120,18 @@ class _Material(object):
def print_material(self): def print_material(self):
print("Material properties:") print("Material properties:")
print("------------------------") print("------------------------")
print("Chemical formula: {}".format(self._chemical_formula)) print("Chemical formula: {}".format(self.chemical_formula))
if self._numeric_density: if self.numeric_density:
print("Numeric Density: {}".format(self._numeric_density)) print("Numeric Density: {}".format(self.numeric_density))
else: else:
print("Numeric Density: Set from elemental properties by Mantid") print("Numeric Density: Set from elemental properties by Mantid")
self._print_material_properties() self._print_material_properties()
def _print_material_properties(self): def _print_material_properties(self):
if self._is_material_props_set: if self._is_material_props_set:
print("Absorption cross section: {}".format(self._absorption_cross_section)) print("Absorption cross section: {}".format(self.absorption_cross_section))
print("Scattering cross section: {}".format(self._scattering_cross_section)) print("Scattering cross section: {}".format(self.scattering_cross_section))
else: else:
print("Absorption cross section: Calculated by Mantid based on chemical/elemental formula") print("Absorption cross section: Calculated by Mantid based on chemical/elemental formula")
print("Scattering 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): ...@@ -143,8 +146,8 @@ class _Material(object):
_check_value_is_physical("absorption_cross_section", abs_cross_sect) _check_value_is_physical("absorption_cross_section", abs_cross_sect)
_check_value_is_physical("scattering_cross_section", scattering_cross_sect) _check_value_is_physical("scattering_cross_section", scattering_cross_sect)
self._absorption_cross_section = float(abs_cross_sect) self.absorption_cross_section = float(abs_cross_sect)
self._scattering_cross_section = float(scattering_cross_sect) self.scattering_cross_section = float(scattering_cross_sect)
self._is_material_props_set = True self._is_material_props_set = True
......
...@@ -112,7 +112,7 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): ...@@ -112,7 +112,7 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check that we can only set a material once. We will test the underlying class elsewhere # Check that we can only set a material once. We will test the underlying class elsewhere
sample_details_obj.set_material(chemical_formula='V') 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 # Check that the material is now immutable
with assertRaisesRegex(self, RuntimeError, "The material has already been set to the above details"): with assertRaisesRegex(self, RuntimeError, "The material has already been set to the above details"):
...@@ -120,16 +120,16 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): ...@@ -120,16 +120,16 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check resetting it works # Check resetting it works
sample_details_obj.reset_sample_material() 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 # And ensure setting it for a second time works
sample_details_obj.set_material(chemical_formula='V') 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): def test_set_material_properties(self):
sample_details_obj = sample_details.SampleDetails(height=1.0, radius=1.0, center=[2, 3, 5]) 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 # Check we cannot set a material property without setting the underlying material
with assertRaisesRegex(self, RuntimeError, "The material has not been set"): with assertRaisesRegex(self, RuntimeError, "The material has not been set"):
...@@ -148,24 +148,24 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): ...@@ -148,24 +148,24 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
material_obj_one_char = sample_details._Material(chemical_formula=chemical_formula_one_char_element) material_obj_one_char = sample_details._Material(chemical_formula=chemical_formula_one_char_element)
self.assertIsNotNone(material_obj_one_char) self.assertIsNotNone(material_obj_one_char)
self.assertEqual(material_obj_one_char._chemical_formula, chemical_formula_one_char_element) self.assertEqual(material_obj_one_char.chemical_formula, chemical_formula_one_char_element)
self.assertIsNone(material_obj_one_char._numeric_density) self.assertIsNone(material_obj_one_char.numeric_density)
# Also check that the absorption and scattering X sections have not been set # 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.absorption_cross_section)
self.assertIsNone(material_obj_one_char._scattering_cross_section) self.assertIsNone(material_obj_one_char.scattering_cross_section)
self.assertFalse(material_obj_one_char._is_material_props_set) self.assertFalse(material_obj_one_char._is_material_props_set)
# Check if it accepts two character elements without numeric density # Check if it accepts two character elements without numeric density
material_obj_two_char = sample_details._Material(chemical_formula=chemical_formula_two_char_element) material_obj_two_char = sample_details._Material(chemical_formula=chemical_formula_two_char_element)
self.assertIsNotNone(material_obj_two_char) self.assertIsNotNone(material_obj_two_char)
self.assertEqual(material_obj_two_char._chemical_formula, chemical_formula_two_char_element) self.assertEqual(material_obj_two_char.chemical_formula, chemical_formula_two_char_element)
self.assertIsNone(material_obj_two_char._numeric_density) self.assertIsNone(material_obj_two_char.numeric_density)
# Check it stores numeric density if passed # Check it stores numeric density if passed
material_obj_numeric_density = sample_details._Material(chemical_formula=chemical_formula_two_char_element, material_obj_numeric_density = sample_details._Material(chemical_formula=chemical_formula_two_char_element,
numeric_density=numeric_density_sample) 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 # 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"): with assertRaisesRegex(self, ValueError, "A numeric density formula must be set on a chemical formula"):
...@@ -174,8 +174,8 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): ...@@ -174,8 +174,8 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check it constructs if it is given the numeric density too # Check it constructs if it is given the numeric density too
material_obj_num_complex_formula = sample_details._Material(chemical_formula=chemical_formula_complex, material_obj_num_complex_formula = sample_details._Material(chemical_formula=chemical_formula_complex,
numeric_density=numeric_density_sample) numeric_density=numeric_density_sample)
self.assertEqual(material_obj_num_complex_formula._chemical_formula, chemical_formula_complex) 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.numeric_density, numeric_density_sample)
def test_material_set_properties(self): def test_material_set_properties(self):
bad_absorb = '-1' bad_absorb = '-1'
...@@ -196,20 +196,20 @@ class ISISPowderSampleDetailsTest(unittest.TestCase): ...@@ -196,20 +196,20 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=bad_scattering) material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=bad_scattering)
# Check nothing has been set yet # Check nothing has been set yet
self.assertIsNone(material_obj._absorption_cross_section) self.assertIsNone(material_obj.absorption_cross_section)
self.assertIsNone(material_obj._scattering_cross_section) self.assertIsNone(material_obj.scattering_cross_section)
# Set the object this time # Set the object this time
material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=good_scattering) material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=good_scattering)
self.assertTrue(material_obj._is_material_props_set) self.assertTrue(material_obj._is_material_props_set)
self.assertEqual(material_obj._absorption_cross_section, float(good_absorb)) self.assertEqual(material_obj.absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj._scattering_cross_section, float(good_scattering)) self.assertEqual(material_obj.scattering_cross_section, float(good_scattering))
# Check we cannot set it twice and fields do not change # Check we cannot set it twice and fields do not change
with assertRaisesRegex(self, RuntimeError, "The material properties have already been set"): with assertRaisesRegex(self, RuntimeError, "The material properties have already been set"):
material_obj.set_material_properties(abs_cross_sect=999, scattering_cross_sect=999) 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.absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj._scattering_cross_section, float(good_scattering)) self.assertEqual(material_obj.scattering_cross_section, float(good_scattering))
def test_print_sample_details(self): def test_print_sample_details(self):
expected_height = 1 expected_height = 1
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment