def create_transformation(trans_type: TransformationType):
    file = NexusWrapper(str(uuid.uuid4()))
    ds = file.nexus_file.create_dataset("transform", data=8)
    t = Transformation(file, ds)
    t.type = trans_type
    t.vector = QVector3D(1, 0, 0)
    return t
def test_dependents_list_is_created_by_instrument(file, nexus_wrapper):
    """
    The dependents list for transforms is stored in the "dependent_of" attribute,
    which is not part of the NeXus standard,
    we therefore cannot rely on it being present and correct in a file we load.
    This test makes sure that instrument generates this information in the wrapped NeXus file it is given.
    """

    # Create minimal test file with some transformations but no "dependent_of" attributes
    entry_group = file.create_group("entry")
    entry_group.attrs["NX_class"] = "NXentry"
    instrument_group = entry_group.create_group("instrument")
    instrument_group.attrs["NX_class"] = "NXinstrument"
    transforms_group = instrument_group.create_group("transformations")
    transforms_group.attrs["NX_class"] = "NXtransformations"
    transform_1 = transforms_group.create_dataset("transform_1", data=42)
    transform_2 = transforms_group.create_dataset("transform_2", data=42)
    transform_3 = transforms_group.create_dataset("transform_3", data=42)
    transform_4 = transforms_group.create_dataset("transform_4", data=42)
    transform_2.attrs["depends_on"] = transform_1.name
    transform_3.attrs["depends_on"] = transform_2.name
    transform_4.attrs["depends_on"] = transform_2.name

    nexus_wrapper.load_file(entry_group, file)
    Instrument(nexus_wrapper, NX_CLASS_DEFINITIONS)

    transform_1_loaded = Transformation(nexus_wrapper, transform_1)
    assert (
        len(transform_1_loaded.dependents) == 1
    ), "Expected transform 1 to have a registered dependent (transform 2)"

    transform_2_loaded = Transformation(nexus_wrapper, transform_2)
    assert (
        len(transform_2_loaded.dependents) == 2
    ), "Expected transform 2 to have 2 registered dependents (transforms 3 and 4)"
Beispiel #3
0
def test_transform_dependents_depends_on_are_updated_when_transformation_name_is_changed(
    nexus_wrapper, ):

    test_name = "slartibartfast"
    test_value = 42
    test_vector = QVector3D(1.0, 0.0, 0.0)
    test_type = "Translation"

    transform_dataset = _add_transform_to_file(nexus_wrapper, test_name,
                                               test_value, test_vector,
                                               test_type)

    component = nexus_wrapper.create_nx_group("test", "NXaperture",
                                              nexus_wrapper.nexus_file)

    component.create_dataset("depends_on", data=transform_dataset.name)

    transform = Transformation(nexus_wrapper, transform_dataset)
    transform.register_dependent(Component(nexus_wrapper, component))

    new_name = test_name + "1"

    transform.name = new_name

    assert transform.name == new_name
    assert str(component["depends_on"][()],
               encoding="UTF-8") == transform.dataset.name
 def add_rotation(
     self,
     axis: QVector3D,
     angle: float,
     name: str = None,
     depends_on: Transformation = None,
 ) -> Transformation:
     """
     Note, currently assumes angle is in degrees
     :param axis: axis
     :param angle:
     :param name: Name of the rotation group (Optional)
     :param depends_on: existing transformation which the new one depends on (otherwise relative to origin)
     """
     transforms_group = self.file.create_transformations_group_if_does_not_exist(
         self.group)
     if name is None:
         name = _generate_incremental_name(TransformationType.ROTATION,
                                           transforms_group)
     field = self.file.set_field_value(transforms_group, name, angle, float)
     self.file.set_attribute_value(field, CommonAttrs.UNITS, "degrees")
     self.file.set_attribute_value(field, CommonAttrs.VECTOR,
                                   qvector3d_to_numpy_array(axis))
     self.file.set_attribute_value(field, CommonAttrs.TRANSFORMATION_TYPE,
                                   TransformationType.ROTATION)
     rotation_transform = Transformation(self.file, field)
     rotation_transform.depends_on = depends_on
     rotation_transform.ui_value = angle
     return rotation_transform
    def add_translation(self,
                        vector: QVector3D,
                        name: str = None,
                        depends_on: Transformation = None) -> Transformation:
        """
        Note, currently assumes translation is in metres
        :param vector: direction and magnitude of translation as a 3D vector
        :param name: name of the translation group (Optional)
        :param depends_on: existing transformation which the new one depends on (otherwise relative to origin)
        """
        transforms_group = self.file.create_transformations_group_if_does_not_exist(
            self.group)
        if name is None:
            name = _generate_incremental_name(TransformationType.TRANSLATION,
                                              transforms_group)
        unit_vector, magnitude = _normalise(vector)
        field = self.file.set_field_value(transforms_group, name, magnitude,
                                          float)
        self.file.set_attribute_value(field, CommonAttrs.UNITS, "m")
        self.file.set_attribute_value(field, CommonAttrs.VECTOR,
                                      qvector3d_to_numpy_array(unit_vector))
        self.file.set_attribute_value(field, CommonAttrs.TRANSFORMATION_TYPE,
                                      TransformationType.TRANSLATION)

        translation_transform = Transformation(self.file, field)
        translation_transform.ui_value = magnitude
        translation_transform.depends_on = depends_on
        return translation_transform
 def _get_transform(
     self,
     depends_on: str,
     transforms: List[Transformation],
     local_only: bool = False,
 ):
     """
     Recursive function, appends each transform in depends_on chain to transforms list
     :param depends_on: The next depends_on string to find the next transformation in the chain
     :param transforms: The list to populate with transformations
     :param local_only: If True then only add transformations which are stored within this component
     """
     if depends_on is not None and depends_on != ".":
         transform_dataset = self.file.nexus_file[depends_on]
         if (local_only and transform_dataset.parent.parent.name !=
                 self.absolute_path):
             # We're done, the next transformation is not stored in this component
             return
         new_transform = Transformation(self.file, transform_dataset)
         new_transform.parent = transforms
         transforms.append(new_transform)
         if CommonAttrs.DEPENDS_ON in transform_dataset.attrs.keys():
             self._get_transform(
                 self.file.get_attribute_value(transform_dataset,
                                               CommonAttrs.DEPENDS_ON),
                 transforms,
                 local_only,
             )
 def refresh_depends_on(_, node):
     """
     Refresh the depends_on attribute of each transformation, which also results in registering dependents
     """
     if isinstance(node, h5py.Group):
         if CommonAttrs.NX_CLASS in node.attrs.keys():
             if node.attrs[CommonAttrs.NX_CLASS] == "NXtransformations":
                 for transformation_name in node:
                     transform = Transformation(
                         self.nexus, node[transformation_name]
                     )
                     transform.depends_on = transform.depends_on
    def depends_on(self, transformation: Transformation):
        existing_depends_on = self.file.get_attribute_value(
            self.group, CommonAttrs.DEPENDS_ON)
        if existing_depends_on is not None:
            Transformation(
                self.file,
                self.file[existing_depends_on]).deregister_dependent(self)

        if transformation is None:
            self.file.set_field_value(self.group, CommonAttrs.DEPENDS_ON, ".",
                                      str)
        else:
            self.file.set_field_value(self.group, CommonAttrs.DEPENDS_ON,
                                      transformation.absolute_path, str)
            transformation.register_dependent(self)
Beispiel #9
0
def test_can_get_rotation_as_4_by_4_matrix(nexus_wrapper):

    test_value = 45.0  # degrees
    test_vector = QVector3D(0.0, 1.0, 0.0)  # around y-axis
    test_type = "Rotation"
    dataset = _add_transform_to_file(nexus_wrapper, "test_transform",
                                     test_value, test_vector, test_type)
    transformation = Transformation(nexus_wrapper, dataset)

    test_matrix = transformation.qmatrix
    # for a rotation around the y-axis:
    test_value_radians = np.deg2rad(test_value)
    expected_matrix = np.array((
        np.cos(-test_value_radians),
        0,
        np.sin(-test_value_radians),
        0,
        0,
        1,
        0,
        0,
        -np.sin(-test_value_radians),
        0,
        np.cos(-test_value_radians),
        0,
        0,
        0,
        0,
        1,
    ))
    assert np.allclose(expected_matrix,
                       np.array(test_matrix.data()),
                       atol=1.0e-7)
Beispiel #10
0
def create_transform(nexus_file, name):
    initial_value = 42
    initial_vector = QVector3D(1.0, 0.0, 0.0)
    initial_type = "Translation"
    dataset = _add_transform_to_file(nexus_file, name, initial_value,
                                     initial_vector, initial_type)
    return Transformation(nexus_file, dataset)
Beispiel #11
0
def test_dependee_of_contains_both_components_when_generating_dependee_of_chain_with_mixture_of_absolute_and_relative_paths(
        file,  # noqa: F811
):
    entry_group = file.create_group("entry")
    entry_group.attrs["NX_class"] = "NXentry"
    instrument_group = entry_group.create_group("instrument")
    instrument_group.attrs["NX_class"] = "NXinstrument"

    component_a = instrument_group.create_group("a")
    component_a.attrs["NX_class"] = "NXaperture"
    transforms_group = component_a.create_group("Transforms1")
    transform_1 = transforms_group.create_dataset("transform1", data=1.0)
    # Relative path to transform
    component_a.create_dataset("depends_on", data="Transforms1/transform1")

    component_b = instrument_group.create_group("b")
    component_b.attrs["NX_class"] = "NXaperture"
    # Absolute path to transform
    component_b.create_dataset(
        "depends_on", data="/entry/instrument/a/Transforms1/transform1")

    nexus_wrapper = NexusWrapper("test_dependent_transforms_1")
    nexus_wrapper.load_file(entry_group, file)
    Instrument(nexus_wrapper, NX_CLASS_DEFINITIONS)
    transform_1_loaded = Transformation(nexus_wrapper, transform_1)

    # Check both relative and absolute are in dependee_of list
    assert component_a.name in transform_1_loaded.dataset.attrs[
        "NCdependee_of"]
    assert component_b.name in transform_1_loaded.dataset.attrs[
        "NCdependee_of"]
Beispiel #12
0
def test_multiple_relative_transform_paths_are_converted_to_absolute_path_in_dependee_of_field(
        file, nexus_wrapper):
    component_name = "component_1"

    component1 = add_component_to_file(nexus_wrapper,
                                       component_name=component_name)
    # make depends_on point to relative transformations group
    component1.group["depends_on"] = "transformations/transform1"

    transformations_group = component1.group.create_group("transformations")

    transform1_name = "transform1"
    transform1_dataset = transformations_group.create_dataset(transform1_name,
                                                              data=1)
    transform1_dataset.attrs[CommonAttrs.VECTOR] = qvector3d_to_numpy_array(
        QVector3D(1, 0, 0))
    transform1_dataset.attrs[
        CommonAttrs.TRANSFORMATION_TYPE] = TransformationType.TRANSLATION

    transform2_name = "transform2"

    # make transform1 depends_on point to relative transform in same directory
    transform1_dataset.attrs["depends_on"] = transform2_name

    transform2_dataset = transformations_group.create_dataset(transform2_name,
                                                              data=2)
    transform2_dataset.attrs[CommonAttrs.VECTOR] = qvector3d_to_numpy_array(
        QVector3D(1, 1, 0))
    transform2_dataset.attrs[
        CommonAttrs.TRANSFORMATION_TYPE] = TransformationType.TRANSLATION

    # make sure the depends_on points to the absolute path of the transform it depends on in the file
    assert (Transformation(
        nexus_wrapper,
        transform1_dataset).depends_on.dataset.name == transform2_dataset.name)
Beispiel #13
0
def test_can_get_transform_properties(nexus_wrapper):

    test_name = "slartibartfast"
    test_value = 42
    test_vector = QVector3D(1.0, 0.0, 0.0)
    test_type = "Translation"

    transform_dataset = _add_transform_to_file(nexus_wrapper, test_name,
                                               test_value, test_vector,
                                               test_type)

    transform = Transformation(nexus_wrapper, transform_dataset)

    assert (
        transform.name == test_name
    ), "Expected the transform name to match what was in the NeXus file"
    assert (
        transform.ui_value == test_value
    ), "Expected the transform value to match what was in the NeXus file"
    assert (
        transform.vector == test_vector
    ), "Expected the transform vector to match what was in the NeXus file"
    assert (
        transform.type == test_type
    ), "Expected the transform type to match what was in the NeXus file"
Beispiel #14
0
def test_transform_type_is_capitalised(test_input, nexus_wrapper):
    test_name = "slartibartfast"
    test_value = 42
    test_vector = QVector3D(1.0, 0.0, 0.0)
    transform_dataset = _add_transform_to_file(nexus_wrapper, test_name,
                                               test_value, test_vector,
                                               test_input)
    transform = Transformation(nexus_wrapper, transform_dataset)
    assert transform.type == "Translation"
Beispiel #15
0
def test_can_get_translation_as_4_by_4_matrix(nexus_wrapper):

    test_value = 42.0
    # Note, it should not matter if this is not set to a unit vector
    test_vector = QVector3D(2.0, 0.0, 0.0)
    test_type = "Translation"
    dataset = _add_transform_to_file(nexus_wrapper, "test_transform",
                                     test_value, test_vector, test_type)
    transformation = Transformation(nexus_wrapper, dataset)

    test_matrix = transformation.qmatrix
    expected_matrix = np.array(
        (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, test_value, 0, 0, 1))
    assert np.allclose(expected_matrix, np.array(test_matrix.data()))
Beispiel #16
0
def test_ui_value_for_transform_with_array_magnitude_returns_first_value(
        nexus_wrapper):
    transform_name = "transform1"
    array = [1.1, 2.2, 3.3]
    transform_value = np.asarray(array, dtype=float)

    transform_dataset = _add_transform_to_file(
        nexus_wrapper,
        transform_name,
        transform_value,
        QVector3D(1, 0, 0),
        TransformationType.TRANSLATION,
    )

    transformation = Transformation(nexus_wrapper, transform_dataset)
    assert transformation.ui_value == array[0]
Beispiel #17
0
def test_ui_value_for_transform_with_array_magnitude_of_strings_returns_zero(
    nexus_wrapper, ):
    transform_name = "transform1"
    array = ["a1", "b1", "c1"]
    transform_value = np.asarray(array, dtype=h5py.special_dtype(vlen=str))

    transform_dataset = _add_transform_to_file(
        nexus_wrapper,
        transform_name,
        transform_value,
        QVector3D(1, 0, 0),
        TransformationType.TRANSLATION,
    )

    transformation = Transformation(nexus_wrapper, transform_dataset)
    assert transformation.ui_value == 0
    def remove_transformation(self, transform: Transformation):
        if not self._transform_is_in_this_component(transform):
            raise PermissionError(
                "Transform is not in this component, do not have permission to delete"
            )

        dependents = transform.get_dependents()
        if dependents:
            raise DependencyError(
                f"Cannot delete transformation, it is a dependency of {dependents}"
            )

        # Remove whole transformations group if this is the only transformation in it
        if len(transform._dataset.parent.keys()) == 1:
            self.file.delete_node(transform._dataset.parent)
        # Otherwise just remove the transformation from the group
        else:
            self.file.delete_node(transform._dataset)
def test_dependent_is_created_by_instrument_if_depends_on_is_relative(
        file, nexus_wrapper):
    entry_group = file.create_group("entry")
    entry_group.attrs["NX_class"] = "NXentry"
    monitor_group = entry_group.create_group("monitor1")
    monitor_group.attrs["NX_class"] = "NXmonitor"

    monitor_group.create_dataset("depends_on",
                                 data=b"transformations/translation1")

    transformations_group = monitor_group.create_group("transformations")
    transformations_group.attrs["NX_class"] = "NXtransformations"
    transform_1 = transformations_group.create_dataset("translation1", data=1)

    nexus_wrapper.load_file(entry_group, file)
    Instrument(nexus_wrapper, NX_CLASS_DEFINITIONS)

    transform_1_loaded = Transformation(nexus_wrapper, transform_1)
    assert transform_1_loaded.dataset.attrs["NCdependee_of"][
        0] == "/entry/monitor1"
Beispiel #20
0
def test_transforms_with_no_dependees_return_None_for_depends_on(
        file, nexus_wrapper):
    component_name = "component_1"

    component1 = add_component_to_file(nexus_wrapper,
                                       component_name=component_name)
    # make depends_on point to relative transformations group
    component1.group["depends_on"] = "transformations/transform1"

    transformations_group = component1.group.create_group("transformations")

    transform1_name = "transform1"
    transform1_dataset = transformations_group.create_dataset(transform1_name,
                                                              data=1)
    transform1_dataset.attrs[CommonAttrs.VECTOR] = qvector3d_to_numpy_array(
        QVector3D(1, 0, 0))
    transform1_dataset.attrs[
        CommonAttrs.TRANSFORMATION_TYPE] = TransformationType.TRANSLATION

    transform1_dataset.attrs["depends_on"] = "."
    transformation = Transformation(nexus_wrapper, transform1_dataset)

    assert not transformation.depends_on
 def depends_on(self):
     depends_on_path = self.file.get_field_value(self.group,
                                                 CommonAttrs.DEPENDS_ON)
     if depends_on_path is None:
         return None
     return Transformation(self.file, self.file.nexus_file[depends_on_path])