From 65fee4068b836a7bea3082f0f38c7efa58dbe433 Mon Sep 17 00:00:00 2001 From: Ben Webb Date: Fri, 18 Oct 2024 23:55:14 -0700 Subject: [PATCH] Check for missing translation or rotation When writing out transformations (for geometric objects or datasets) report an error if the transformation is missing either a rotation or a translation. Skip this check if so requested. --- ihm/dumper.py | 17 ++++++++++---- ihm/util/__init__.py | 7 ++++++ test/test_dumper.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/ihm/dumper.py b/ihm/dumper.py index 9f5ccc7..b12c89c 100644 --- a/ihm/dumper.py +++ b/ihm/dumper.py @@ -62,10 +62,15 @@ def dump(self, system, writer): def _get_transform(rot_matrix, tr_vector): """Return a dict encoding a transform, suitable for passing to loop.write()""" - # mmCIF writer usually outputs floats to 3 decimal - # places, but we need more precision for rotation - # matrices - rm = [["%.6f" % e for e in rot_matrix[i]] for i in range(3)] + if rot_matrix in (None, ihm.unknown): + rm = [[rot_matrix for _ in range(3)] for _ in range(3)] + else: + # mmCIF writer usually outputs floats to 3 decimal + # places, but we need more precision for rotation + # matrices + rm = [["%.6f" % e for e in rot_matrix[i]] for i in range(3)] + if tr_vector in (None, ihm.unknown): + tr_vector = [tr_vector for _ in range(3)] return {'rot_matrix11': rm[0][0], 'rot_matrix21': rm[1][0], 'rot_matrix31': rm[2][0], 'rot_matrix12': rm[0][1], @@ -1242,6 +1247,8 @@ def dump_related_transform(self, system, writer): "rot_matrix[1][3]", "rot_matrix[2][3]", "rot_matrix[3][3]", "tr_vector[1]", "tr_vector[2]", "tr_vector[3]"]) as lp: for t in self._transform_by_id: + if self._check: + util._check_transform(t) lp.write(id=t._dtid, **_get_transform(t.rot_matrix, t.tr_vector)) @@ -2085,6 +2092,8 @@ def dump_transformations(self, writer): "rot_matrix[1][3]", "rot_matrix[2][3]", "rot_matrix[3][3]", "tr_vector[1]", "tr_vector[2]", "tr_vector[3]"]) as lp: for t in self._transformations_by_id: + if self._check: + util._check_transform(t) lp.write(id=t._id, **_get_transform(t.rot_matrix, t.tr_vector)) def dump_generic(self, writer): diff --git a/ihm/util/__init__.py b/ihm/util/__init__.py index 5ec41e1..d33ca9f 100644 --- a/ihm/util/__init__.py +++ b/ihm/util/__init__.py @@ -92,6 +92,13 @@ def _check_residue(r): % (r.seq_id, r.entity, len(r.entity.sequence))) +def _check_transform(t): + if t.rot_matrix in (None, ihm.unknown): + raise ValueError("Transformation %s is missing rotation" % t) + if t.tr_vector in (None, ihm.unknown): + raise ValueError("Transformation %s is missing translation" % t) + + def _invert_ranges(ranges, end, start=1): """Given a sorted list of non-overlapping ranges, yield a new list which contains every range in the range start-end which was not in the diff --git a/test/test_dumper.py b/test/test_dumper.py index b794af0..933a9ce 100644 --- a/test/test_dumper.py +++ b/test/test_dumper.py @@ -1694,6 +1694,29 @@ def test_dataset_dumper_dump(self): # """) + def test_dataset_dumper_dump_invalid_transform(self): + """Test DatasetDumper.dump() with invalid transformed dataset""" + system = ihm.System() + + loc = ihm.location.PDBLocation('1cdf', version='foo', details='bar') + loc._id = 1 + dst = ihm.dataset.Dataset(loc, details='baz') + t = ihm.geometry.Transformation( + rot_matrix=None, tr_vector=[1., 2., 3.]) + td = ihm.dataset.TransformedDataset(dst, transform=t) + + loc = ihm.location.InputFileLocation(repo='foo', path='bar') + loc._id = 2 + ds2 = ihm.dataset.CXMSDataset(loc) + ds2.parents.append(td) + system.orphan_datasets.append(ds2) + + d = ihm.dumper._DatasetDumper() + d.finalize(system) # Assign IDs + self.assertRaises(ValueError, _get_dumper_output, d, system) + # OK if checks are disabled + _ = _get_dumper_output(d, system, check=False) + def test_model_representation_dump(self): """Test ModelRepresentationDumper""" system = ihm.System() @@ -3576,6 +3599,39 @@ def test_geometric_object_dumper(self): # """) + def test_geometric_object_dumper_invalid_rotation(self): + """Test GeometricObjectDumper with invalid rotation""" + system = ihm.System() + center = ihm.geometry.Center(1., 2., 3.) + trans = ihm.geometry.Transformation(None, [1., 2., 3.]) + sphere = ihm.geometry.Sphere(center=center, transformation=trans, + radius=2.2, name='my sphere', + description='a test sphere') + system.orphan_geometric_objects.append(sphere) + + dumper = ihm.dumper._GeometricObjectDumper() + dumper.finalize(system) + self.assertRaises(ValueError, _get_dumper_output, dumper, system) + # OK if checks are disabled + _ = _get_dumper_output(dumper, system, check=False) + + def test_geometric_object_dumper_invalid_translation(self): + """Test GeometricObjectDumper with invalid translation""" + system = ihm.System() + center = ihm.geometry.Center(1., 2., 3.) + trans = ihm.geometry.Transformation([[1, 0, 0], [0, 1, 0], [0, 0, 1]], + ihm.unknown) + sphere = ihm.geometry.Sphere(center=center, transformation=trans, + radius=2.2, name='my sphere', + description='a test sphere') + system.orphan_geometric_objects.append(sphere) + + dumper = ihm.dumper._GeometricObjectDumper() + dumper.finalize(system) + self.assertRaises(ValueError, _get_dumper_output, dumper, system) + # OK if checks are disabled + _ = _get_dumper_output(dumper, system, check=False) + def test_feature_dumper(self): """Test FeatureDumper""" system = ihm.System()