Skip to content

Commit

Permalink
nifti-mrs package depndency + hyper handling (Philips) (#59)
Browse files Browse the repository at this point in the history
* Initial move to nifti-mrs package.

* Fix conjugation added with nifti_mrs package.

* Add hyper handling to spar/sdat pathway.

* Fix twix issues identified by CJD.

* First pass at data/list HYPER handling.

* Add commandline special case parameter.

* Add version info

* Add hyper handling to data list, add hyper tests on philips.

* Renable bruker conversion pathway and tests.

* Typo in requirements.

* Reduce restrictions in dependency.

* Touch up bruker tests.

* Touch up bruker tests.
  • Loading branch information
wtclarke authored Jan 18, 2023
1 parent f57bed2 commit add956e
Show file tree
Hide file tree
Showing 30 changed files with 624 additions and 860 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
This document contains the Spec2nii release history in reverse chronological order.

0.6.1 (Wednesday 18 January 2023)
---------------------------------
- Fixed conjugation issue introduced by new nifti-mrs package dependency
- SPAR/SDAT pipeline now handles HYPER special case.
- Data/list pipeline now handles HYPER special case.
- Fixed issue with XA Twix PatientSex and TxOffset attributes.
- Reenable Bruker conversion.

0.6.0 (Wednesday 11th January 2023)
-----------------------------------
- NIfTI-MRS creation/handling/verification now performed by nifti-mrs package.

0.5.0 (Friday 2nd December 2022)
--------------------------------
- NIfTI-MRS version 0.6
Expand Down
3 changes: 2 additions & 1 deletion requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ dependencies:
- pydicom
- pyMapVBVD>=0.5.2
- scipy
- brukerapi>=0.1.4.0
- brukerapi>=0.1.8
- pandas
- nifti-mrs>=0.1.3
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
'spec2nii.Siemens',
'spec2nii.GSL',
'spec2nii.dcm2niiOrientation',
'spec2nii.nifti_mrs',
'spec2nii.Philips',
'spec2nii.GE'],
install_requires=install_requires,
Expand Down
13 changes: 8 additions & 5 deletions spec2nii/GE/ge_pfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
# 3rd party modules
import numpy as np

from nifti_mrs.create_nmrs import gen_nifti_mrs_hdr_ext
from nifti_mrs.hdr_ext import Hdr_Ext

# Our modules
from spec2nii.GE.ge_read_pfile import Pfile
from spec2nii import nifti_mrs
from spec2nii.nifti_orientation import NIFTIOrient
from spec2nii import __version__ as spec2nii_ver

Expand Down Expand Up @@ -104,7 +106,7 @@ def _process_svs_pfile(pfile):

out_nmrs = []
for dd, mm in zip(data, meta):
out_nmrs.append(nifti_mrs.NIfTI_MRS(dd, orientation.Q44, dwelltime, mm))
out_nmrs.append(gen_nifti_mrs_hdr_ext(dd, dwelltime, mm, orientation.Q44, no_conj=True))

return out_nmrs, fname_suffix

Expand Down Expand Up @@ -228,7 +230,7 @@ def fft_and_shift(x, axis):
meta = _populate_metadata(pfile)
orientation = NIFTIOrient(_calculate_affine_mrsi(pfile))

return [nifti_mrs.NIfTI_MRS(data, orientation.Q44, dwelltime, meta), ], ['', ]
return [gen_nifti_mrs_hdr_ext(data, dwelltime, meta, orientation.Q44, no_conj=True), ], ['', ]


def _calculate_affine_mrsi(pfile):
Expand Down Expand Up @@ -297,8 +299,9 @@ def _populate_metadata(pfile, water_suppressed=True):
'Use the --override_nucleus option to specify a different nuclide.')
nucleus = "1H"

meta = nifti_mrs.hdr_ext(spec_frequency,
nucleus)
meta = Hdr_Ext(
spec_frequency,
nucleus)

# Standard defined metadata
# # 5.1 MRS specific Tags
Expand Down
92 changes: 73 additions & 19 deletions spec2nii/Philips/philips.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@
from ast import literal_eval

import numpy as np
from nifti_mrs.create_nmrs import gen_nifti_mrs_hdr_ext
from nifti_mrs.hdr_ext import Hdr_Ext

from spec2nii.nifti_orientation import NIFTIOrient, calc_affine
from spec2nii import nifti_mrs
from spec2nii import __version__ as spec2nii_ver


def read_sdat_spar_pair(sdat_file, spar_file, shape=None, tags=None):
"""_summary_
_extended_summary_
:param sdat_file: _description_
:type sdat_file: _type_
:param spar_file: _description_
:type spar_file: _type_
:param shape: _description_, defaults to None
:type shape: _type_, optional
:param tags: _description_, defaults to None
:type tags: _type_, optional
:return: _description_
:rtype: _type_
def read_sdat_spar_pair(sdat_file, spar_file, shape=None, tags=None, fileout=None, special=None):
"""Read and convert SPAR/SDAT pairs from Philips scanners
:param sdat_file: Path to .SDAT file
:type sdat_file: pathlib.Path
:param spar_file: Path to .SPAR file
:type spar_file: pathlib.Path
:param shape: List of dimension shapes to reshape array, defaults to None
:type shape: list of ints, optional
:param tags: List of higher dimension tags, defaults to None
:type tags: List of strings, optional
:param fileout: File name string passed by user, defaults to None/stem of file
:type fileout: str, optional
:param special: Identifier for special-cased sequence, defaults to None
:type special: str, optional
:return: List of NIFTI-MRS objects
:rtype: list of nifti_mrs.nifti_mrs.NIFTI_MRS
:return: List of file name parts
:rtype: list of strings
"""
spar_params = read_spar(spar_file)
data = read_sdat(sdat_file,
Expand Down Expand Up @@ -62,7 +67,21 @@ def read_sdat_spar_pair(sdat_file, spar_file, shape=None, tags=None):
affine = np.diag(np.array([10000, 10000, 10000, 1]))
orientation = NIFTIOrient(affine)

return [nifti_mrs.NIfTI_MRS(data, orientation.Q44, dwelltime, meta), ]
# name of output
if fileout is not None:
mainStr = fileout
else:
mainStr = sdat_file.stem

# Special cases
if spar_params['scan_id'].lower() == 'hyper'\
or (special is not None and
special.lower() == 'hyper'):
return _special_case_hyper(data, dwelltime, meta, orientation.Q44, mainStr)
else:
# Normal case
return [gen_nifti_mrs_hdr_ext(data, dwelltime, meta, orientation.Q44, no_conj=True), ],\
[mainStr, ]


def read_spar(filename):
Expand Down Expand Up @@ -143,8 +162,9 @@ def spar_to_nmrs_hdrext(spar_dict):

# Extract required metadata and create hdr_ext object
cf = float(spar_dict["synthesizer_frequency"]) / 1E6
obj = nifti_mrs.hdr_ext(cf,
spar_dict["nucleus"])
obj = Hdr_Ext(
cf,
spar_dict["nucleus"])

def set_standard_def(nifti_mrs_key, location, key, cast=None):
try:
Expand Down Expand Up @@ -266,3 +286,37 @@ def _vax_to_ieee_single_float(data):
# may want to raise an exception here ...

return f


def _special_case_hyper(data, dwelltime, meta, orientation, fout_str):
# Reorganise the data. This unfortunately makes hardcoded assumptions about the size of each part.
data_short_te = data[:, :, :, :, :32]
data_edited = data[:, :, :, :, 32:]
data_edited = data_edited.T.reshape((56, 4, data.shape[3], 1, 1, 1)).T

meta_short_te = meta.copy()
meta_edited = meta.copy()

edit_pulse_1 = 1.9
edit_pulse_2 = 4.58
edit_pulse_off = 4.18
dim_info = "HERCULES j-difference editing, four conditions"
dim_header = {"EditCondition": ["A", "B", "C", "D"]}
edit_pulse_val = {
"A": {"PulseOffset": [edit_pulse_1, edit_pulse_2], "PulseDuration": 0.02},
"B": {"PulseOffset": [edit_pulse_off, edit_pulse_2], "PulseDuration": 0.02},
"C": {"PulseOffset": edit_pulse_1, "PulseDuration": 0.02},
"D": {"PulseOffset": edit_pulse_off, "PulseDuration": 0.02}}

meta_edited.set_dim_info(
0,
'DIM_EDIT',
dim_info,
dim_header)

meta_edited.set_dim_info(1, 'DIM_DYN')
meta_edited.set_standard_def("EditPulse", edit_pulse_val)

return [gen_nifti_mrs_hdr_ext(data_short_te, dwelltime, meta_short_te, orientation, no_conj=True),
gen_nifti_mrs_hdr_ext(data_edited, dwelltime, meta_edited, orientation, no_conj=True)],\
[fout_str + '_hyper_short_te', fout_str + '_hyper_edited']
Loading

0 comments on commit add956e

Please sign in to comment.