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
44 changes: 44 additions & 0 deletions brukerapi/config/properties_fid_proc_core.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"pv_version": [
{
"cmd": "(#ACQ_sw_version).replace('PV-','').replace('PV ','').strip()",
"conditions": []
},
{
"cmd": "''",
"conditions": []
}
],
"numpy_dtype": [
{
"cmd": "np.dtype('f8').newbyteorder('>' if #BYTORDA=='big' else '<')",
"conditions": [],
"comment": "spec 3.5: fid_proc.64 / fid_refscan.64 hold 64-bit doubles, whatever the raw acquisition word type is. The .64 suffix is the word size"
}
],
"shape_final": [
{
"cmd": "(int(np.prod(np.atleast_1d(#PVM_SpecMatrix))),)",
"conditions": [],
"comment": "spec 3.5: one complex float64 pair per spectral point, so the file is 8 * 2 * PVM_SpecMatrix bytes"
},
{
"cmd": "(int(file_size(self.path) // @numpy_dtype.itemsize // 2),)",
"conditions": [],
"comment": "PVM_SpecMatrix is a method parameter and need not be there; fall back on the file itself"
}
],
"shape_storage": [
{
"cmd": "(2 * @shape_final[0],)",
"conditions": [],
"comment": "real and imaginary interleaved"
}
],
"dim_type": [
{
"cmd": ["'k_space_encode_step_0'"],
"conditions": []
}
]
}
63 changes: 63 additions & 0 deletions brukerapi/config/properties_fid_proc_custom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"subj_id": [
{
"cmd": "#SUBJECT_id",
"conditions": []
},
{
"cmd": "''",
"conditions": []
}
],
"study_id": [
{
"cmd": "str(#SUBJECT_study_nr)",
"conditions": []
},
{
"cmd": "''",
"conditions": []
}
],
"exp_id": [
{
"cmd": "@path.parents[2].name",
"conditions": []
},
{
"cmd": "''",
"conditions": []
}
],
"proc_id": [
{
"cmd": "@path.parents[0].name",
"conditions": []
},
{
"cmd": "''",
"conditions": []
}
],
"id": [
{
"cmd": "'{}_{}_{}_{}_{}'.format(@type.upper(), @exp_id, @proc_id, @subj_id, @study_id)",
"conditions": [],
"comment": "spec 3.5: these live in the PROCNO, so the id names the reconstruction as a 2dseq id does"
}
],
"TE": [
{
"cmd": "#PVM_EchoTime",
"conditions": [],
"unit": "ms"
}
],
"TR": [
{
"cmd": "#PVM_RepetitionTime",
"conditions": [],
"unit": "ms"
}
]
}
64 changes: 37 additions & 27 deletions brukerapi/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,18 @@
"load": LOAD_STAGES["all"],
"mmap": False,
},
# Spec 3.5: the PV360 spectroscopy PROCNO files, 64-bit doubles with real and
# imaginary interleaved -- not the acquisition word type, and not the fid
# block model, so they get recipes of their own.
"fid_proc": {
"parameter_files": ["acqp", "method"],
"property_files": [Path(__file__).parents[0] / "config/properties_fid_core.json", Path(__file__).parents[0] / "config/properties_fid_custom.json"],
"property_files": [Path(__file__).parents[0] / "config/properties_fid_proc_core.json", Path(__file__).parents[0] / "config/properties_fid_proc_custom.json"],
"load": LOAD_STAGES["all"],
"mmap": False,
},
"fid_refscan": {
"parameter_files": ["acqp", "method"],
"property_files": [Path(__file__).parents[0] / "config/properties_fid_proc_core.json", Path(__file__).parents[0] / "config/properties_fid_proc_custom.json"],
"load": LOAD_STAGES["all"],
"mmap": False,
},
Expand Down Expand Up @@ -116,6 +125,18 @@
"AdjStatePerScan": "../../AdjStatePerScan",
"AdjStatePerStudy": "../../../AdjStatePerStudy",
},
"fid_refscan": {
"method": "../../method",
"acqp": "../../acqp",
"subject": "../../../subject",
"reco": "./reco",
"methreco": "./methreco",
"pvmeta": "./pvmeta",
"d3proc": "./d3proc",
"visu_pars": "./visu_pars",
"AdjStatePerScan": "../../AdjStatePerScan",
"AdjStatePerStudy": "../../../AdjStatePerStudy",
},
"2dseq": {
"method": "../../method",
"acqp": "../../acqp",
Expand Down Expand Up @@ -174,6 +195,7 @@
SUPPORTED_SUBTYPES = {
"fid": {""},
"fid_proc": {"64"},
"fid_refscan": {"64"},
"2dseq": {""},
"traj": {""},
"rawdata": {"Navigator"},
Expand Down Expand Up @@ -245,11 +267,7 @@ def __init__(self, path, **state):
(name for name in content if re.fullmatch(r"rawdata\.job\d+", name)),
key=lambda name: int(name.rsplit("job", 1)[1]),
)
named_rawdata_jobs = sorted(
name
for name in content
if name not in rawdata_jobs and self.is_supported_path(self.path / name, {"rawdata"})
)
named_rawdata_jobs = sorted(name for name in content if name not in rawdata_jobs and self.is_supported_path(self.path / name, {"rawdata"}))
if "fid" in content:
self.path = self.path / "fid"
elif "2dseq" in content:
Expand All @@ -273,10 +291,7 @@ def __init__(self, path, **state):
if self.type not in DEFAULT_STATES:
raise UnsupportedDatasetType(self.type)
if self.type == "fid" and self.subtype in FID_COMPANION_SUBTYPES and not state.get("_auxiliary"):
raise UnsupportedDatasetType(
f"{self.path.name} is a fid companion (spec §3.5); load it as "
f"Dataset({with_suffix(self.path, '')!s}).fid_companions[{self.subtype!r}]"
)
raise UnsupportedDatasetType(f"{self.path.name} is a fid companion (spec §3.5); load it as Dataset({with_suffix(self.path, '')!s}).fid_companions[{self.subtype!r}]")
if not self._is_supported_subtype() and not (state.get("_auxiliary") and self.type == "fid" and self.subtype in FID_COMPANION_SUBTYPES):
raise UnsupportedDatasetType(self.path.name)

Expand Down Expand Up @@ -529,7 +544,7 @@ def _read_parameters(self):
reco_path = self._state.get("reco_path")
if reco_path is not None:
self.add_parameter_file(reco_path)
elif self.type in {"fid", "fid_proc"}:
elif self.type == "fid":
with suppress(FileNotFoundError):
self.add_parameter_file("reco")

Expand Down Expand Up @@ -596,9 +611,9 @@ def get(self, name, default=None):
A name no configuration declares still raises, so a typo does not
quietly become `default`::

dataset.get("TE") # 4.0, or None where there is no echo time
dataset.get("TE", "n/a") # 4.0, or "n/a"
dataset.get("TEE") # AttributeError
dataset.get("TE") # 4.0, or None where there is no echo time
dataset.get("TE", "n/a") # 4.0, or "n/a"
dataset.get("TEE") # AttributeError

Parameters are a separate layer, read with ``dataset[key]`` or
``dataset.parameters``.
Expand Down Expand Up @@ -727,8 +742,7 @@ def rawdata_stored_scans(self):
settings_stored = int(settings[9])
if settings_stored != job_stored and not getattr(self, "_warned_rawdata_stored_scans", False):
warnings.warn(
f"ACQ_ScanPipeJobSettings nStoredScans={settings_stored} disagrees with "
f"ACQ_jobs nStoredScans={job_stored} for {self.path}; using the settings value",
f"ACQ_ScanPipeJobSettings nStoredScans={settings_stored} disagrees with ACQ_jobs nStoredScans={job_stored} for {self.path}; using the settings value",
RuntimeWarning,
stacklevel=2,
)
Expand Down Expand Up @@ -779,9 +793,7 @@ def _infer_scheme_id(self):
if descriptions and descriptions[0] == "Spectroscopic":
return "SPECTROSCOPY" if int(dim) == 1 else "CSI"

if n_projections is not None and int(n_projections) > 0 and (
(self.path.parent / "traj").exists() or "RADIAL" in family or "UTE" in family
):
if n_projections is not None and int(n_projections) > 0 and ((self.path.parent / "traj").exists() or "RADIAL" in family or "UTE" in family):
return "RADIAL"

acq_size = np.atleast_1d(self._parameter_value("ACQ_size", []))
Expand Down Expand Up @@ -860,8 +872,10 @@ def load_schema(self):
"""
Load the schema for given data set.
"""
if self.type in ["fid", "fid_proc"]:
if self.type == "fid":
self._schema = SchemaFid(self)
elif self.type in {"fid_proc", "fid_refscan"}:
self._schema = SchemaFidCompanion(self)
elif self.type == "2dseq":
self._schema = Schema2dseq(self)
elif self.type == "rawdata":
Expand Down Expand Up @@ -940,9 +954,7 @@ def _read_binary_file(self, path, dtype, shape):
f"expected {expected_bytes} bytes for shape {tuple(shape)} and dtype {dtype}"
)

raise InvalidDataset(
f"Invalid dataset size at {path}: expected {expected_bytes} bytes for shape {tuple(shape)} and dtype {dtype}, got {actual_bytes} bytes"
)
raise InvalidDataset(f"Invalid dataset size at {path}: expected {expected_bytes} bytes for shape {tuple(shape)} and dtype {dtype}, got {actual_bytes} bytes")

return read_array(path, dtype, shape)

Expand Down Expand Up @@ -1343,9 +1355,7 @@ def affine(self):
"""
descriptors = np.atleast_1d(np.asarray(self._parameter_value("VisuCoreDimDesc", []))).astype(str)
if descriptors.size and any(descriptor != "spatial" for descriptor in descriptors):
raise UnsupportedDatasetType(
f"an image affine for {self.path}, whose frames are {sorted(set(descriptors))} rather than purely spatial (spec 7.2),"
)
raise UnsupportedDatasetType(f"an image affine for {self.path}, whose frames are {sorted(set(descriptors))} rather than purely spatial (spec 7.2),")
if len(self.slice_packages_index()) > 1:
warnings.warn(
f"{self.path} has multiple slice packages; a single affine cannot describe them -- use get_slice_packages() / affine_of_package(i)",
Expand Down Expand Up @@ -1405,7 +1415,7 @@ def to_kspace(self, *, bart=False):
API: EPI and non-Cartesian raw data require acquisition-specific
handling.
"""
if self.type not in {"fid", "fid_proc", "rawdata"}:
if self.type not in {"fid", "rawdata"}:
raise UnsupportedDatasetType(f"k-space conversion is not available for {self.type} datasets")
return self._schema.to_kspace(bart=bart)

Expand Down
Loading