|
1 |
| -from typing import Any |
| 1 | +__all__ = [ |
| 2 | + 'ReflectionsMetadata', |
| 3 | + 'MTZBatchMetadata', |
| 4 | + 'MTZDatasetMetadata', |
| 5 | + 'MTZReflectionsMetadata', |
| 6 | + 'CIFReflectionsMetadata', |
| 7 | + 'ReflectionsMetadataType' |
| 8 | +] |
| 9 | + |
| 10 | +from typing import Any, Sequence, overload |
2 | 11 |
|
3 | 12 | import gemmi
|
| 13 | +from pydantic import computed_field |
4 | 14 |
|
5 | 15 | from xtl.common.options import Option, Options
|
6 | 16 | from xtl.common.serializers import GemmiUnitCell, GemmiSpaceGroup, GemmiMat33
|
7 |
| -from xtl.diffraction.reflections.files import ReflectionsFileType |
| 17 | +from xtl.diffraction.reflections.files import ReflectionsFileType, GemmiCIF2MTZSpec |
8 | 18 |
|
9 | 19 |
|
10 | 20 | class ReflectionsMetadata(Options):
|
@@ -151,7 +161,7 @@ class MTZReflectionsMetadata(ReflectionsMetadata):
|
151 | 161 | 'missing values (VALM)')
|
152 | 162 | history: tuple[str, ...] | None = Option(default=None, desc='History lines')
|
153 | 163 |
|
154 |
| - @property |
| 164 | + @computed_field(description='Whether the reflections are merged or unmerged') |
155 | 165 | def is_merged(self) -> bool | None:
|
156 | 166 | """
|
157 | 167 | Check if the reflections are merged or unmerged. The presence of batches in the
|
@@ -185,23 +195,79 @@ def from_gemmi(cls, mtz: gemmi.Mtz):
|
185 | 195 | )
|
186 | 196 |
|
187 | 197 |
|
188 |
| -class CIFDatasetMetadata(Options): |
189 |
| - wavelength: float | None = Option(default=None, gt=0.0, |
190 |
| - desc='Wavelength in Angstroms') |
191 |
| - unit_cell: gemmi.UnitCell | None = Option(default=None, formatter=GemmiUnitCell, |
192 |
| - desc='Unit cell parameters in ' |
193 |
| - 'Angstroms/degrees') |
| 198 | +class CIFReflectionsMetadata(ReflectionsMetadata): |
| 199 | + """ |
| 200 | + Metadata for reflections extracted from a CIF file. |
| 201 | + """ |
194 | 202 |
|
| 203 | + origin_file_type: ReflectionsFileType = Option(default=ReflectionsFileType.CIF, |
| 204 | + desc='Type of the file where the ' |
| 205 | + 'reflections originated from') |
| 206 | + entry_id: str | None = Option(default=None, desc='Entry ID of the CIF block') |
| 207 | + is_merged: bool | None = Option(default=None, desc='Whether the reflections are ' |
| 208 | + 'merged or unmerged') |
| 209 | + spec_lines: tuple[GemmiCIF2MTZSpec, ...] | None = \ |
| 210 | + Option(default_factory=tuple, |
| 211 | + formatter=lambda x: tuple(l.line for l in x), |
| 212 | + desc='List of specification lines used during CIF to MTZ conversion') |
| 213 | + |
| 214 | + @computed_field(description='Column types inferred from the column labels using ' |
| 215 | + 'the specification lines, if available') |
| 216 | + def column_types_inferred(self) -> tuple[str | None, ...] | None: |
| 217 | + """ |
| 218 | + Infer the column types from the column labels using the specification lines, |
| 219 | + if available. |
| 220 | + """ |
| 221 | + column_types = [] |
| 222 | + if not self.column_labels or not self.spec_lines: |
| 223 | + return None |
| 224 | + |
| 225 | + specs = list(self.spec_lines) + \ |
| 226 | + [GemmiCIF2MTZSpec.from_line(l) for l in |
| 227 | + ['index_h H H 0', 'index_k K H 0', 'index_l L H 0']] |
| 228 | + for label in self.column_labels: |
| 229 | + inferred = False |
| 230 | + for spec in specs: |
| 231 | + if label == spec.tag: |
| 232 | + column_types.append(spec.column_type) |
| 233 | + inferred = True |
| 234 | + break |
| 235 | + if not inferred: |
| 236 | + column_types.append(None) |
| 237 | + |
| 238 | + return tuple(column_types) |
| 239 | + |
| 240 | + @classmethod |
| 241 | + def from_gemmi(cls, rblock: gemmi.ReflnBlock, |
| 242 | + spec_lines: Sequence[str | GemmiCIF2MTZSpec] = None): |
| 243 | + """ |
| 244 | + Create an instance from a ``gemmi.ReflnBlock`` object. |
| 245 | + """ |
| 246 | + if not isinstance(rblock, gemmi.ReflnBlock): |
| 247 | + raise TypeError(f'Expected gemmi.ReflnBlock, got {type(rblock).__name__}') |
| 248 | + |
| 249 | + resolution_high = rblock.block.find_value('_reflns.d_resolution_high') |
| 250 | + resolution_low = rblock.block.find_value('_reflns.d_resolution_low') |
| 251 | + n_obs = rblock.block.find_value('_reflns.number_obs') |
| 252 | + resolution_high = float(resolution_high) if resolution_high else None |
| 253 | + resolution_low = float(resolution_low) if resolution_low else None |
| 254 | + n_obs = int(n_obs) if n_obs else None |
| 255 | + |
| 256 | + return cls( |
| 257 | + origin_file_type=ReflectionsFileType.CIF, |
| 258 | + name=rblock.block.name, |
| 259 | + entry_id=rblock.entry_id, |
| 260 | + unit_cell=rblock.cell, |
| 261 | + space_group=rblock.spacegroup, |
| 262 | + wavelength=rblock.wavelength, |
| 263 | + resolution_high=resolution_high, |
| 264 | + resolution_low=resolution_low, |
| 265 | + no_reflections=n_obs, |
| 266 | + is_merged=rblock.is_merged(), |
| 267 | + column_labels=tuple(rblock.column_labels()), |
| 268 | + spec_lines=spec_lines or tuple() |
| 269 | + ) |
195 | 270 |
|
196 |
| -class CIFReflectionsMetadata(ReflectionsMetadata): |
197 |
| - origin_file_type: ReflectionsFileType = Option(default=ReflectionsFileType.CIF) |
198 |
| - datasets: list[CIFDatasetMetadata] | None = Option(default=None) |
199 |
| - title: str | None = Option(default=None, max_length=64, |
200 |
| - desc='Title of the CIF file') |
201 |
| - spec_lines: list[str] | None = Option(default=None, |
202 |
| - desc='List of specification lines') |
203 |
| - history: list[str] | None = Option(default=None, |
204 |
| - desc='History of the CIF file') |
205 | 271 |
|
206 | 272 |
|
207 | 273 | ReflectionsMetadataType = ReflectionsMetadata | MTZReflectionsMetadata | \
|
|
0 commit comments