Skip to content

Commit d578a56

Browse files
committed
Enhances CIF parsing support in experiments: 1st step
1 parent eba4c70 commit d578a56

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

src/easydiffraction/experiments/experiment/factory.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors <https://github.com/easyscience/diffraction>
22
# SPDX-License-Identifier: BSD-3-Clause
33

4+
import gemmi
5+
46
from easydiffraction.core.factory import FactoryBase
57
from easydiffraction.experiments.categories.experiment_type import ExperimentType
68
from easydiffraction.experiments.experiment import BraggPdExperiment
79
from easydiffraction.experiments.experiment import BraggScExperiment
810
from easydiffraction.experiments.experiment import TotalPdExperiment
11+
from easydiffraction.experiments.experiment.base import ExperimentBase
912
from easydiffraction.experiments.experiment.enums import BeamModeEnum
1013
from easydiffraction.experiments.experiment.enums import RadiationProbeEnum
1114
from easydiffraction.experiments.experiment.enums import SampleFormEnum
@@ -67,18 +70,75 @@ def create(cls, **kwargs):
6770
elif 'name' in kwargs:
6871
return cls._create_without_data(kwargs)
6972

73+
# -------------
74+
# gemmi helpers
75+
# -------------
76+
77+
# TODO: Move to a common CIF utility module? io.cif.parse?
78+
@staticmethod
79+
def _read_cif_document_from_path(path: str) -> gemmi.cif.Document:
80+
"""Read a CIF document from a file path."""
81+
return gemmi.cif.read_file(path)
82+
83+
# TODO: Move to a common CIF utility module? io.cif.parse?
84+
@staticmethod
85+
def _read_cif_document_from_string(text: str) -> gemmi.cif.Document:
86+
"""Read a CIF document from a raw text string."""
87+
return gemmi.cif.read_string(text)
88+
89+
# TODO: Move to a common CIF utility module? io.cif.parse?
90+
@classmethod
91+
def _pick_first_block(
92+
cls,
93+
doc: gemmi.cif.Document,
94+
) -> gemmi.cif.Block:
95+
"""Pick the first experimental block from a CIF document."""
96+
try:
97+
return doc.sole_block()
98+
except Exception:
99+
return doc[0]
100+
101+
# TODO: Move to a common CIF utility module? io.cif.parse?
102+
@classmethod
103+
def _extract_name_from_block(cls, block: gemmi.cif.Block) -> str:
104+
"""Extract a model name from the CIF block name."""
105+
return block.name or 'model'
106+
107+
# TODO: Move to a common CIF utility module? io.cif.parse?
108+
@classmethod
109+
def _create_experiment_from_block(
110+
cls,
111+
block: gemmi.cif.Block,
112+
) -> ExperimentBase:
113+
"""Build a model instance from a single CIF block."""
114+
name = cls._extract_name_from_block(block)
115+
116+
# TODO: experiment type need to be read from CIF block
117+
kwargs = {'beam_mode': BeamModeEnum.CONSTANT_WAVELENGTH}
118+
expt_type = cls._make_experiment_type(kwargs)
119+
120+
# Create experiment instance of appropriate class
121+
scattering_type = expt_type.scattering_type.value
122+
sample_form = expt_type.sample_form.value
123+
expt_class = cls._SUPPORTED[scattering_type][sample_form]
124+
expt_obj = expt_class(name=name, type=expt_type)
125+
126+
# TODO: Read all categories from CIF block into experiment
127+
128+
# TODO: Read data from CIF block into experiment datastore
129+
130+
return expt_obj
131+
70132
# -------------------------------
71133
# Private creation helper methods
72134
# -------------------------------
73135

74-
@staticmethod
75-
def _create_from_cif_path(cif_path):
76-
"""Create an experiment from a CIF file path.
77-
78-
Not yet implemented.
79-
"""
80-
# TODO: Implement CIF file loading logic
81-
raise NotImplementedError('CIF file loading not implemented yet.')
136+
@classmethod
137+
def _create_from_cif_path(cls, cif_path):
138+
"""Create an experiment from a CIF file path."""
139+
doc = cls._read_cif_document_from_path(cif_path)
140+
block = cls._pick_first_block(doc)
141+
return cls._create_experiment_from_block(block)
82142

83143
@staticmethod
84144
def _create_from_cif_str(cif_str):

src/easydiffraction/sample_models/sample_model/factory.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ def _create_from_cif_path(
5959
cif_path: str,
6060
) -> SampleModelBase:
6161
"""Create a model by reading and parsing a CIF file."""
62-
# Parse CIF and build model
6362
doc = cls._read_cif_document_from_path(cif_path)
6463
block = cls._pick_first_structural_block(doc)
6564
return cls._create_model_from_block(block)
@@ -70,7 +69,6 @@ def _create_from_cif_str(
7069
cif_str: str,
7170
) -> SampleModelBase:
7271
"""Create a model by parsing a CIF string."""
73-
# Parse CIF string and build model
7472
doc = cls._read_cif_document_from_string(cif_str)
7573
block = cls._pick_first_structural_block(doc)
7674
return cls._create_model_from_block(block)
@@ -81,16 +79,19 @@ def _create_from_cif_str(
8179
# gemmi helpers
8280
# -------------
8381

82+
# TODO: Move to a common CIF utility module? io.cif.parse?
8483
@staticmethod
8584
def _read_cif_document_from_path(path: str) -> gemmi.cif.Document:
8685
"""Read a CIF document from a file path."""
8786
return gemmi.cif.read_file(path)
8887

88+
# TODO: Move to a common CIF utility module? io.cif.parse?
8989
@staticmethod
9090
def _read_cif_document_from_string(text: str) -> gemmi.cif.Document:
9191
"""Read a CIF document from a raw text string."""
9292
return gemmi.cif.read_string(text)
9393

94+
# TODO: Move to a common CIF utility module? io.cif.parse?
9495
@staticmethod
9596
def _has_structural_content(block: gemmi.cif.Block) -> bool:
9697
"""Return True if the CIF block contains structural content."""
@@ -108,6 +109,7 @@ def _has_structural_content(block: gemmi.cif.Block) -> bool:
108109
]
109110
return all(block.find_value(tag) for tag in required_cell)
110111

112+
# TODO: Move to a common CIF utility module? io.cif.parse?
111113
@classmethod
112114
def _pick_first_structural_block(
113115
cls,
@@ -124,6 +126,7 @@ def _pick_first_structural_block(
124126
except Exception:
125127
return doc[0]
126128

129+
# TODO: Move to a common CIF utility module? io.cif.parse?
127130
@classmethod
128131
def _create_model_from_block(
129132
cls,
@@ -137,11 +140,13 @@ def _create_model_from_block(
137140
cls._set_atom_sites_from_cif_block(model, block)
138141
return model
139142

143+
# TODO: Move to a common CIF utility module? io.cif.parse?
140144
@classmethod
141145
def _extract_name_from_block(cls, block: gemmi.cif.Block) -> str:
142146
"""Extract a model name from the CIF block name."""
143147
return block.name or 'model'
144148

149+
# TODO: Move to a common CIF utility module? io.cif.parse?
145150
@classmethod
146151
def _set_space_group_from_cif_block(
147152
cls,
@@ -151,6 +156,7 @@ def _set_space_group_from_cif_block(
151156
"""Populate the model's space group from a CIF block."""
152157
model.space_group.from_cif(block)
153158

159+
# TODO: Move to a common CIF utility module? io.cif.parse?
154160
@classmethod
155161
def _set_cell_from_cif_block(
156162
cls,
@@ -160,6 +166,7 @@ def _set_cell_from_cif_block(
160166
"""Populate the model's unit cell from a CIF block."""
161167
model.cell.from_cif(block)
162168

169+
# TODO: Move to a common CIF utility module? io.cif.parse?
163170
@classmethod
164171
def _set_atom_sites_from_cif_block(
165172
cls,
@@ -168,3 +175,7 @@ def _set_atom_sites_from_cif_block(
168175
) -> None:
169176
"""Populate the model's atom sites from a CIF block."""
170177
model.atom_sites.from_cif(block)
178+
179+
# TODO: How to automatically parce and populate all categories?
180+
# for category in model.categories:
181+
# cls._set_category_from_cif_block(category, block)

tmp/_read_cif.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
# %%
6464
project.experiments.show_names()
6565

66+
# %%
67+
experiment.show_as_cif()
68+
6669
# %%
6770
experiment.instrument.setup_wavelength = 1.494
6871
experiment.instrument.calib_twotheta_offset = 0.6
@@ -90,6 +93,9 @@
9093
# %% [markdown]
9194
# ## Step 4: Perform Analysis
9295

96+
# %%
97+
experiment.show_as_cif()
98+
9399
# %%
94100
sample_model.cell.length_a.free = True
95101

0 commit comments

Comments
 (0)