Source code for console.interfaces.acquisition_parameter
"""Interface class for acquisition parameters."""
import pickle # noqa: S403
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any
from console.interfaces.dimensions import Dimensions
from console.interfaces.enums import DDCMethod
def _grad_factory() -> Dimensions:
return Dimensions(x=0., y=0., z=0.)
def _fov_factory() -> Dimensions:
return Dimensions(x=1., y=1., z=1.)
[docs]
@dataclass(unsafe_hash=True)
class AcquisitionParameter:
"""
Parameters to define an acquisition.
The acquisition parameters are defined in a dataclass which is hashable but still mutable.
This allows to easily change parameters and detect updates by comparing the hash.
New instances of acquisition parameters are not saved automatically.
Once the autosave flag is set by calling `activate_autosave`, the parameter state is
written to a pickle file acquisition-parameter.state in the default_state_file_path on any
mutation of the acquisition parameter instance.
The autosave option can be deactivated calling `deactivate_autosave`.
Manually storing the acquisition parameters to a specific directory can be achieved
using the `save` method and providing the desired path.
"""
larmor_frequency: float = 2.e6
"""Larmor frequency in MHz."""
b1_scaling: float = 1.0
"""Scaling of the B1 field (RF transmit power)."""
gradient_offset: Dimensions = field(default_factory=_grad_factory)
"""Gradient offset values in mV."""
fov_scaling: Dimensions = field(default_factory=_fov_factory)
"""Field of view scaling for Gx, Gy and Gz."""
decimation: int = 200
"""Decimation rate for initial down-sampling step."""
ddc_method: DDCMethod = DDCMethod.FIR
num_averages: int = 1
"""Number of acquisition averages."""
averaging_delay: float = 0.0
"""Delay in seconds between acquisition averages."""
state_filepath: Path | str = Path.home() / Path("nexus-console/acquisition-parameter.state")
"""Default file path for acquisition parameter state."""
_initialized: bool = field(default=False, init=True, repr=False, compare=False, hash=False)
def __post_init__(self):
"""Post initialization method."""
if isinstance(self.state_filepath, str):
self.state_filepath = Path(self.state_filepath)
if not self.state_filepath.name.endswith(".state"):
self.state_filepath = self.state_filepath / "acquisition-parameter.state"
# Load state file if it already exists
if self.state_filepath.exists():
with self.state_filepath.open(mode="rb") as state_file:
state = pickle.load(state_file) # noqa: S301
self.__dict__.update(**state)
self._initialized = True
self.save()
def __setattr__(self, name: str, value: Any) -> None:
"""Overwrite __setattr__ function to save object on each mutation."""
if self._initialized:
_hash = hash(self)
super().__setattr__(name, value)
if hash(self) != _hash:
self.save()
else:
super().__setattr__(name, value)
def __str__(self):
"""Get string representation of acquisition parameter."""
data = self.dict()
output = "Acquisition parameter\n----------\n"
for k, (key, value) in enumerate(data.items()):
output += f"{key} = {value}" if k == len(data) - 1 else f"{key} = {value}\n"
return output
def __copy__(self):
"""Copy acquisition parameter."""
cls = self.__class__
result = cls(**self.__dict__)
result._initialized = True
return result
[docs]
def dict(self, use_strings: bool = False) -> dict:
"""Return acquisition parameters as dictionary.
Parameters
----------
use_strings, optional
boolean flag indicating if values of dictionary should be represented as strings, by default False
Returns
-------
Acquisition parameter dictionary
"""
if use_strings:
return {k: str(v) for k, v in asdict(self).items() if not k.startswith("_")}
data = {k: v for k, v in asdict(self).items() if not k.startswith("_")}
# Make state filepath a str (Path variable)
data["state_filepath"] = str(data["state_filepath"])
return data
[docs]
def save(self, filepath: str | Path | None = None) -> None:
"""Save current acquisition parameter state.
Parameters
----------
file_path, optional
Path to the pickle state file, by default None.
If None, the default state file path is taken which is <home>/nexus-console/acquisition-parameter.state
Default state file path can be changed using the set_default_path method.
"""
_filepath = filepath if filepath else self.state_filepath
if isinstance(_filepath, str):
_filepath = Path(_filepath)
if not _filepath.name.endswith(".state"):
_filepath = _filepath / "acquisition-parameter.state"
_filepath.parent.mkdir(parents=True, exist_ok=True)
data = {key: value for key, value in self.__dict__.items() if not key.startswith("_")}
with open(_filepath, "wb") as file:
pickle.dump(data, file)
[docs]
def hash(self) -> int:
"""Return acquisition parameter integer hash."""
return self.__hash__()
[docs]
@classmethod
def load(cls, filepath: Path | str) -> "AcquisitionParameter":
"""Load acquisition parameter state from state file in-place.
Parameters
----------
file_path, optional
Path to acquisition parameter state file.
If file_path is not a pickle file, i.e. ends with .pkl,
the default state file designation acquisition-parameter.state is added.
Returns
-------
Instance of acquisition parameters with state loaded from provided file_path.
Raises
------
FileNotFoundError
Provided file_path is not a pickle file or does not exist.
"""
filepath = Path(filepath) if isinstance(filepath, str) else filepath
if not filepath.exists():
raise FileNotFoundError("Acquisition parameter state file not found: ", filepath)
with open(filepath, "rb") as state_file:
state = pickle.load(state_file) # noqa: S301
instance = cls(**state)
instance._initialized = True
return instance