Skip to content

Commit 04fcf01

Browse files
committed
written ort file reader
1 parent 6f47925 commit 04fcf01

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

RATapi/classlist.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,15 @@ def extend(self, other: Sequence[T]) -> None:
291291
self._check_unique_name_fields(other)
292292
self.data.extend(other)
293293

294+
def union(self, other: Sequence[T]) -> None:
295+
"""Extend the ClassList by a sequence, ignoring input items with names that already exist."""
296+
if other and not (isinstance(other, Sequence) and not isinstance(other, str)):
297+
other = [other]
298+
if hasattr(other, self.name_field):
299+
other = (item for item in other if getattr(other, self.name_field) not in self.get_names())
300+
301+
self.extend(other)
302+
294303
def set_fields(self, index: int, **kwargs) -> None:
295304
"""Assign the values of an existing object's attributes using keyword arguments."""
296305
self._validate_name_field(kwargs)

RATapi/utils/orso.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""Readers from file formats."""
2+
3+
from dataclasses import dataclass
4+
5+
import orsopy
6+
from orsopy.fileio import load_orso
7+
8+
from RATapi import ClassList, Project
9+
from RATapi.models import AbsorptionLayer, Background, Contrast, Data, Layer, Parameter, Resolution
10+
11+
12+
def read_ort(filepath: str) -> Project:
13+
"""Create a project from an .ort file.
14+
15+
Parameters
16+
----------
17+
filepath : str
18+
The path to the .ort file.
19+
20+
Returns
21+
-------
22+
Project
23+
The project data from the .ort file.
24+
"""
25+
project = Project()
26+
27+
ort_data = load_orso(filepath)
28+
29+
for dataset in ort_data:
30+
metadata = dataset.info
31+
sample = metadata.data_source.sample
32+
model_info = orso_model_to_rat(sample.model)
33+
34+
# Add all parameters that aren't already defined
35+
project.parameters.union(model_info.parameters)
36+
project.layers.extend(model_info.layers)
37+
project.bulk_in.append(model_info.bulk_in)
38+
project.bulk_out.append(model_info.bulk_out)
39+
40+
project.data.append(Data(name=sample.name, data=dataset.data))
41+
42+
if dataset.data.shape[1] == 4:
43+
project.resolutions.append(
44+
Resolution(
45+
name=f"{sample.name} Resolution",
46+
type="data",
47+
)
48+
)
49+
else:
50+
project.resolution_parameters.append(Parameter(name=f"{sample.name} Resolution Parameter"))
51+
project.resolutions.append(
52+
Resolution(
53+
name=f"{sample.name} Resolution",
54+
type="constant",
55+
source=f"{sample.name} Resolution Parameter",
56+
)
57+
)
58+
59+
project.background_parameters.append(Parameter(name=f"{sample.name} Background Parameter"))
60+
project.backgrounds.append(
61+
Background(
62+
name=f"{sample.name} Background",
63+
type="constant",
64+
source=f"{sample.name} Background Parameter",
65+
)
66+
)
67+
68+
project.scalefactors.append(Parameter(name=f"{sample.name} Scalefactor"))
69+
70+
project.contrasts.append(
71+
Contrast(
72+
name=metadata.data_source.experiment.title,
73+
data=sample.name,
74+
background=f"{sample.name} Background",
75+
bulk_in=model_info.bulk_in.name,
76+
bulk_out=model_info.bulk_out.name,
77+
scalefactor=f"{sample.name} Scalefactor",
78+
resolution=f"{sample.name} Resolution",
79+
model=[layer.name for layer in model_info.layers],
80+
)
81+
)
82+
83+
return project
84+
85+
86+
@dataclass
87+
class ORSOSample:
88+
"""The stack data from an ORSO SampleModel, in RAT models."""
89+
90+
bulk_in: Parameter
91+
bulk_out: Parameter
92+
parameters: ClassList[Parameter]
93+
layers: ClassList[Layer]
94+
95+
96+
def orso_model_to_rat(model: orsopy.fileio.model_language.SampleModel | str) -> ORSOSample:
97+
"""Get information from an ORSO SampleModel object.
98+
99+
Parameters
100+
----------
101+
model : orsopy.fileio.model_language.SampleModel or str
102+
The sample model to turn into a RAT data. If given as a string,
103+
the string is interpreted as a layer stack in ORSO model language.
104+
105+
Returns
106+
-------
107+
ORSOSample
108+
A dataclass containing the sample data.
109+
110+
"""
111+
if isinstance(model, str):
112+
model = orsopy.fileio.model_language.SampleModel(stack=model)
113+
stack = model.resolve_to_layers()
114+
# if bulk in or out is air, it has SLD predefined
115+
# else we need to grab it from SLDDB
116+
if bulk_in_sld := stack[0].material.sld is None:
117+
bulk_in_sld = stack[0].material.get_sld()
118+
119+
# orsopy SLDs are in 10^-6 inverse square Angstroms
120+
# but RAT uses just inverse square Angstroms
121+
bulk_in_sld *= 1e6
122+
123+
# resolve_to_layers loses the name of bulk in and out
124+
bulk_in_name = model.stack.split("|")[0].strip()
125+
bulk_in = Parameter(
126+
name=f"{bulk_in_name} SLD",
127+
min=bulk_in_sld.real,
128+
value=bulk_in_sld.real,
129+
max=bulk_in_sld.real,
130+
fit=False,
131+
)
132+
133+
if bulk_out_sld := stack[-1].material.sld is None:
134+
bulk_out_sld = stack[-1].material.get_sld()
135+
136+
bulk_out_sld *= 1e6
137+
138+
bulk_out_name = model.stack.split("|")[-1].strip()
139+
bulk_out = Parameter(
140+
name=f"{bulk_out_name} SLD",
141+
min=bulk_out_sld.real,
142+
value=bulk_out_sld.real,
143+
max=bulk_out_sld.real,
144+
fit=False,
145+
)
146+
147+
parameters = ClassList()
148+
layers = ClassList()
149+
150+
for orso_layer in stack[1:-1]:
151+
layer_params, layer = orso_layer_to_rat_layer(orso_layer)
152+
parameters.union(layer_params)
153+
layers.append(layer)
154+
155+
return ORSOSample(bulk_in=bulk_in, bulk_out=bulk_out, parameters=parameters, layers=layers)
156+
157+
158+
def orso_layer_to_rat_layer(
159+
layer: orsopy.fileio.model_language.Layer, absorption: bool = False
160+
) -> tuple[ClassList[Parameter], Layer]:
161+
"""Convert an ``orsopy`` layer to a RAT layer.
162+
163+
Parameters
164+
----------
165+
layer : orsopy.fileio.model_language.Layer
166+
An ``orsopy`` Layer.
167+
absorption : bool, default True
168+
Whether absorption should be accounted for in the layer.
169+
170+
Returns
171+
-------
172+
ClassList[Parameter], Layer
173+
The parameters required for the RAT layer and the layer itself.
174+
175+
"""
176+
name = layer.material.formula
177+
thickness = layer.thickness.as_unit("angstrom")
178+
roughness = layer.roughness.as_unit("angstrom")
179+
180+
# orsopy SLDs are in 10^-6 inverse square Angstroms
181+
# but RAT uses just inverse square Angstroms
182+
sld = layer.material.get_sld() * 1e6
183+
184+
params = ClassList(
185+
[
186+
Parameter(name=f"{name} Thickness", min=thickness, value=thickness, max=thickness, fit=False),
187+
Parameter(name=f"{name} Roughness", min=roughness, value=roughness, max=roughness, fit=False),
188+
Parameter(name=f"{name} SLD", min=sld.real, value=sld.real, max=sld.real, fit=False),
189+
]
190+
)
191+
if absorption:
192+
params.append(Parameter(name=f"{name} SLD imaginary", min=sld.imag, value=sld.imag, max=sld.imag, fit=False))
193+
layer = AbsorptionLayer(
194+
name=f"{name}",
195+
thickness=f"{name} Thickness",
196+
roughness=f"{name} Roughness",
197+
SLD_real=f"{name} SLD",
198+
SLD_imag=f"{name} SLD imaginary",
199+
)
200+
else:
201+
layer = Layer(
202+
name=f"{name}",
203+
thickness=f"{name} Thickness",
204+
roughness=f"{name} Roughness",
205+
SLD=f"{name} SLD",
206+
)
207+
208+
return params, layer

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ StrEnum >= 0.4.15; python_version < '3.11'
1010
ruff >= 0.4.10
1111
scipy >= 1.13.1
1212
tqdm >= 4.66.5
13+
orsopy >= 1.2.1

0 commit comments

Comments
 (0)