Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ quote-style = "single"
"*test_*.py" = ["S101"]

[tool.ruff.lint]
ignore-init-module-imports = true
select = [
# flake8 settings from existing CI setup
"E9", "F63", "F7", "F82",
Expand Down
1 change: 1 addition & 0 deletions src/easyreflectometry/calculators/calculator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from easyscience.fitting.calculators.interface_factory import ItemContainer
from easyscience.io import SerializerComponent

#if TYPE_CHECKING:
from easyreflectometry.model import Model
from easyreflectometry.sample import BaseAssembly
from easyreflectometry.sample import Layer
Expand Down
7 changes: 3 additions & 4 deletions src/easyreflectometry/data/data_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from easyscience.io import SerializerComponent
from easyscience.io import SerializerDict

# from easyscience.utils.io.dict import DictSerializer
from easyreflectometry.model import Model

T = TypeVar('T')
Expand Down Expand Up @@ -77,7 +76,7 @@ def __init__(
y: Optional[Union[np.ndarray, list]] = None,
ye: Optional[Union[np.ndarray, list]] = None,
xe: Optional[Union[np.ndarray, list]] = None,
model: Optional[Model] = None,
model: Optional['Model'] = None, # delay type checking until runtime (quotes)
x_label: str = 'x',
y_label: str = 'y',
):
Expand Down Expand Up @@ -118,11 +117,11 @@ def __init__(
self._color = None

@property
def model(self) -> Model:
def model(self) -> 'Model': # delay type checking until runtime (quotes)
return self._model

@model.setter
def model(self, new_model: Model) -> None:
def model(self, new_model: 'Model') -> None:
self._model = new_model
self._model.background = np.min(self.y)

Expand Down
41 changes: 2 additions & 39 deletions src/easyreflectometry/data/measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@

import numpy as np
import scipp as sc
from orsopy.fileio import Header
from orsopy.fileio import orso

from easyreflectometry.data import DataSet1D
from easyreflectometry.orso_utils import load_data_from_orso_file


def load(fname: Union[TextIO, str]) -> sc.DataGroup:
Expand All @@ -18,7 +17,7 @@ def load(fname: Union[TextIO, str]) -> sc.DataGroup:
:param fname: The file to be read.
"""
try:
return _load_orso(fname)
return load_data_from_orso_file(fname)
except (IndexError, ValueError):
return _load_txt(fname)

Expand All @@ -39,42 +38,6 @@ def load_as_dataset(fname: Union[TextIO, str]) -> DataSet1D:
)


def _load_orso(fname: Union[TextIO, str]) -> sc.DataGroup:
"""Load from an ORSO compatible file.

:param fname: The path for the file to be read.
"""
data = {}
coords = {}
attrs = {}
f_data = orso.load_orso(fname)
for i, o in enumerate(f_data):
name = i
if o.info.data_set is not None:
name = o.info.data_set
coords[f'Qz_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 0],
variances=np.square(o.data[:, 3]),
unit=sc.Unit(o.info.columns[0].unit),
)
try:
data[f'R_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 1],
variances=np.square(o.data[:, 2]),
unit=sc.Unit(o.info.columns[1].unit),
)
except TypeError:
data[f'R_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 1],
variances=np.square(o.data[:, 2]),
)
attrs[f'R_{name}'] = {'orso_header': sc.scalar(Header.asdict(o.info))}
return sc.DataGroup(data=data, coords=coords, attrs=attrs)


def _load_txt(fname: Union[TextIO, str]) -> sc.DataGroup:
"""Load data from a simple txt file.

Expand Down
6 changes: 6 additions & 0 deletions src/easyreflectometry/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@
this_dict['interface'] = self.interface().name
return this_dict

def as_orso(self) -> dict:
"""Convert the model to a dictionary suitable for ORSO."""
this_dict = self.as_dict()

Check warning on line 213 in src/easyreflectometry/model/model.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/model/model.py#L213

Added line #L213 was not covered by tests

return this_dict

Check warning on line 215 in src/easyreflectometry/model/model.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/model/model.py#L215

Added line #L215 was not covered by tests

@classmethod
def from_dict(cls, passed_dict: dict) -> Model:
"""
Expand Down
155 changes: 155 additions & 0 deletions src/easyreflectometry/orso_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import logging

import numpy as np
import scipp as sc
from orsopy.fileio import Header
from orsopy.fileio import model_language
from orsopy.fileio import orso
from orsopy.fileio.base import ComplexValue

from easyreflectometry.data import DataSet1D

from .sample.assemblies.multilayer import Multilayer
from .sample.collections.sample import Sample
from .sample.elements.layers.layer import Layer
from .sample.elements.materials.material import Material
from .sample.elements.materials.material_density import MaterialDensity

# Set up logging
logger = logging.getLogger(__name__)


def LoadOrso(orso_str: str):
"""Load a model from an ORSO file."""

sample = load_orso_model(orso_str)
data = load_orso_data(orso_str)

return sample, data

def load_data_from_orso_file(fname: str) -> sc.DataGroup:
"""Load data from an ORSO file."""
try:
orso_data = orso.load_orso(fname)
except Exception as e:
raise ValueError(f"Error loading ORSO file: {e}")
return load_orso_data(orso_data)

def load_orso_model(orso_str: str) -> Sample:
"""
Load a model from an ORSO file and return a Sample object.

The ORSO file .ort contains information about the sample, saved
as a simple "stack" string, e.g. 'air | m1 | SiO2 | Si'.
This gets parsed by the ORSO library and converted into an ORSO Dataset object.

Args:
orso_str: The ORSO file content as a string

Returns:
Sample: An EasyReflectometry Sample object

Raises:
ValueError: If ORSO layers could not be resolved
"""
# Extract stack string and create ORSO sample model
stack_str = orso_str[0].info.data_source.sample.model.stack
orso_sample = model_language.SampleModel(stack=stack_str)

# Try to resolve layers using different methods
try:
orso_layers = orso_sample.resolve_to_layers()
except ValueError:
orso_layers = orso_sample.resolve_stack()

Check warning on line 63 in src/easyreflectometry/orso_utils.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/orso_utils.py#L62-L63

Added lines #L62 - L63 were not covered by tests

# Handle case where layers are not resolved correctly
if not orso_layers:
raise ValueError("Could not resolve ORSO layers.")

Check warning on line 67 in src/easyreflectometry/orso_utils.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/orso_utils.py#L67

Added line #L67 was not covered by tests

logger.debug(f"Resolved layers: {orso_layers}")

# Convert ORSO layers to EasyReflectometry layers
erl_layers = []
for layer in orso_layers:
erl_layer = _convert_orso_layer_to_erl(layer)
erl_layers.append(erl_layer)

# Create a Multilayer object with the extracted layers
multilayer = Multilayer(erl_layers, name='Multi Layer Sample from ORSO')

# Create Sample from the file
sample_info = orso_str[0].info.data_source.sample
sample_name = sample_info.name if sample_info.name else 'ORSO Sample'
sample = Sample(multilayer, name=sample_name)

return sample


def _convert_orso_layer_to_erl(layer):
"""Helper function to convert an ORSO layer to an EasyReflectometry layer"""
material = layer.material
m_name = material.formula if material.formula is not None else layer.name

# Get SLD values
m_sld, m_isld = _get_sld_values(material, m_name)

# Create and return ERL layer
return Layer(
material=Material(sld=m_sld, isld=m_isld, name=m_name),
thickness=layer.thickness.magnitude if layer.thickness is not None else 0.0,
roughness=layer.roughness.magnitude if layer.roughness is not None else 0.0,
name=layer.original_name if layer.original_name is not None else m_name
)


def _get_sld_values(material, material_name):
"""Extract SLD values from material, calculating from density if needed"""
if material.sld is None and material.mass_density is not None:
# Calculate SLD from mass density
m_density = material.mass_density.magnitude
density = MaterialDensity(
chemical_structure=material_name,
density=m_density
)
m_sld = density.sld.value
m_isld = density.isld.value
else:
if isinstance(material.sld, ComplexValue):
m_sld = material.sld.real
m_isld = material.sld.imag

Check warning on line 119 in src/easyreflectometry/orso_utils.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/orso_utils.py#L118-L119

Added lines #L118 - L119 were not covered by tests
else:
m_sld = material.sld
m_isld = 0.0

return m_sld, m_isld

def load_orso_data(orso_str: str) -> DataSet1D:
data = {}
coords = {}
attrs = {}
for i, o in enumerate(orso_str):
name = i
if o.info.data_set is not None:
name = o.info.data_set
coords[f'Qz_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 0],
variances=np.square(o.data[:, 3]),
unit=sc.Unit(o.info.columns[0].unit),
)
try:
data[f'R_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 1],
variances=np.square(o.data[:, 2]),
unit=sc.Unit(o.info.columns[1].unit),
)
except TypeError:
data[f'R_{name}'] = sc.array(
dims=[f'{o.info.columns[0].name}_{name}'],
values=o.data[:, 1],
variances=np.square(o.data[:, 2]),
)
attrs[f'R_{name}'] = {'orso_header': sc.scalar(Header.asdict(o.info))}
data_group = sc.DataGroup(data=data, coords=coords, attrs=attrs)
return data_group
16 changes: 16 additions & 0 deletions src/easyreflectometry/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,22 @@
self._materials.add_material(Material(name='D2O', sld=6.36, isld=0.0))
return [material.name for material in self._materials].index('D2O')

def load_orso_file(self, path: Union[Path, str]) -> None:
"""Load an ORSO file and optionally create a model and a data from it."""
from easyreflectometry.orso_utils import LoadOrso

Check warning on line 259 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L259

Added line #L259 was not covered by tests

model, data = LoadOrso(path)

Check warning on line 261 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L261

Added line #L261 was not covered by tests
if model is not None:
self.models = ModelCollection([model])

Check warning on line 263 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L263

Added line #L263 was not covered by tests
else:
self.default_model()

Check warning on line 265 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L265

Added line #L265 was not covered by tests
if data is not None:
self._experiments[0] = data
self._experiments[0].name = 'Experiment from ORSO'
self._experiments[0].model = self.models[0]
self._with_experiments = True

Check warning on line 270 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L267-L270

Added lines #L267 - L270 were not covered by tests

pass

Check warning on line 272 in src/easyreflectometry/project.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/project.py#L272

Added line #L272 was not covered by tests
def load_new_experiment(self, path: Union[Path, str]) -> None:
new_experiment = load_as_dataset(str(path))
new_index = len(self._experiments)
Expand Down
22 changes: 12 additions & 10 deletions src/easyreflectometry/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,24 @@ def yaml_dump(dict_repr: dict) -> str:
return yaml.dump(dict_repr, sort_keys=False, allow_unicode=True)


def collect_unique_names_from_dict(structure_dict: dict, unique_names: Optional[list[str]] = None) -> dict:
def collect_unique_names_from_dict(structure_dict: dict, unique_names: Optional[list[str]] = None) -> list[str]:
"""
This function returns a list with the 'unique_name' found the input dictionary.
"""
if unique_names is None:
unique_names = []

if isinstance(structure_dict, dict):
for key, value in structure_dict.items():
if isinstance(value, dict):
collect_unique_names_from_dict(value, unique_names)
elif isinstance(value, list):
for element in value:
collect_unique_names_from_dict(element, unique_names)
if key == 'unique_name':
unique_names.append(value)
def _collect(item):
if isinstance(item, dict):
if 'unique_name' in item:
unique_names.append(item['unique_name'])
for value in item.values():
_collect(value)
elif isinstance(item, list):
for element in item:
_collect(element)

_collect(structure_dict)
return unique_names


Expand Down
Loading
Loading