Skip to content

Commit

Permalink
Merge pull request #41 from AdebayoBraimah/dev5
Browse files Browse the repository at this point in the history
Dev5
  • Loading branch information
AdebayoBraimah committed Jun 25, 2021
2 parents 2edd798 + 39c891d commit e00d7a7
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 90 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ jobs:
steps:
- checkout
- run:
command: python -m pip install pytest pytest-html -r requirements.txt --user
command: python -m pip install pytest pytest-html pytest-cov -r requirements.txt --user
name: Install-deps
- run:
command: chmod -R 755 convert_source/tests
name: Change file permissions
- run:
command: |
pytest --junitxml=test-reports/junit.xml --html=test-reports/pytest_report.html --self-contained-html convert_source/tests
pytest --junitxml=test-reports/junit.xml --html=test-reports/pytest_report.html --self-contained-html --cov=convert_source convert_source/tests
name: Run unit tests
- store_test_results:
path: test-reports
Expand Down
10 changes: 10 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
CHANGES
=========

0.2.0a9
---------

This version is an alpha release that contains several bug fixes and updates.

* BUG FIX: Fixed issue in which multiple converted NIFTI files would throw index error exception during BIDS renaming.
* BUG FIX: Added minimal type checking for several BIDS metadata fields
* BUG FIX: Fixed bug in which if par/nii was in the filepath, the source image file could be misclassified - thus subsequently throwing errors.
* UPDATE: Added option to throw exceptions for PAR multi-echo data. At the moment, without test data correctly handling cases of multi-echo data are not possible for PAR REC data.

0.2.0a8
---------

Expand Down
128 changes: 55 additions & 73 deletions convert_source/batch_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,22 +727,25 @@ def _get_bids_name_args(bids_name_dict: Dict,
return tuple(params_var)

def make_bids_name(bids_name_dict: Dict,
modality_type: str
) -> Tuple[str,str,str,str]:
modality_type: str,
num_imgs: Optional[int] = 4
) -> List[str]:
"""Creates a BIDS compliant filename given a BIDS name description dictionary, and the modality type.
Usage example:
>>> make_bids_name(bids_name_dict=bids_dict,
... modality_type="anat")
... modality_type="anat",
... num_imgs=1)
...
"sub-001_ses-001_run-01_T1w"
Arguments:
bids_name_dict: BIDS name description dictionary.
modality_type: Modality type.
num_imgs: Number of medical images to be re-named.
Returns:
BIDS compliant filename string.
List of BIDS compliant filenames.
"""

bids_keys: List[str] = list(bids_name_dict[modality_type].keys())
Expand Down Expand Up @@ -785,75 +788,56 @@ def make_bids_name(bids_name_dict: Dict,
if rec and ('rec' in bids_keys):
f_name += f"_rec-{rec}"

# Set multiple run names
run1: str = str(run)
run2: str = add_to_zeropadded(run,1)
run3: str = add_to_zeropadded(run,2)
run4: str = add_to_zeropadded(run,3)

f_name1: str = f_name + f"_run-{run1}"
f_name2: str = f_name + f"_run-{run2}"
f_name3: str = f_name + f"_run-{run3}"
f_name4: str = f_name + f"_run-{run4}"

if echo and ('echo' in bids_keys):
f_name1 += f"_echo-{echo}"
f_name2 += f"_echo-{echo}"
f_name3 += f"_echo-{echo}"
f_name4 += f"_echo-{echo}"
# Set name list in the case of
# multiple images
name_list: List[str] = []

for run_num in range(0,num_imgs):
run_id: str = add_to_zeropadded(run,run_num)
f_name_tmp: str = f_name + f"_run-{run_id}"

if echo and ('echo' in bids_keys):
f_name_tmp += f_name_tmp + f"_echo-{echo}"

name_list.append(f_name_tmp)

if modality_type.lower() == 'fmap':
if case1 and mag2:
f_name1: str = f_name1 + "_phasediff"
f_name2: str = f_name1 + "_magnitude1"
f_name3: str = f_name1 + "_magnitude2"
return (f_name1,
f_name2,
f_name3,
"")
f_name1: str = name_list[0] + "_phasediff"
f_name2: str = name_list[0] + "_magnitude1"
f_name3: str = name_list[0] + "_magnitude2"
return ([f_name1,
f_name2,
f_name3])
elif case1:
f_name1: str = f_name1 + "_phasediff"
f_name2: str = f_name1 + "_magnitude1"
return (f_name1,
f_name2,
"",
"")
f_name1: str = name_list[0] + "_phasediff"
f_name2: str = name_list[0] + "_magnitude1"
return ([f_name1,
f_name2])
elif case2:
f_name1: str = f_name1 + "_phase1"
f_name2: str = f_name1 + "_phase2"
f_name3: str = f_name1 + "_magnitude1"
f_name4: str = f_name1 + "_magnitude2"
return (f_name1,
f_name2,
f_name3,
f_name4)
f_name1: str = name_list[0] + "_phase1"
f_name2: str = name_list[0] + "_phase2"
f_name3: str = name_list[0] + "_magnitude1"
f_name4: str = name_list[0] + "_magnitude2"
return ([f_name1,
f_name2,
f_name3,
f_name4])
elif case3:
f_name1: str = f_name1 + "_magnitude"
f_name2: str = f_name1 + "_fieldmap"
return (f_name1,
f_name2,
"",
"")
f_name1: str = name_list[0] + "_magnitude"
f_name2: str = name_list[0] + "_fieldmap"
return ([f_name1,
f_name2])
elif case4:
modality_label = bids_name_dict[modality_type]['modality_label']
f_name1 += f"_{modality_label}"
f_name2 += f"_{modality_label}"
f_name3 += f"_{modality_label}"
f_name4 += f"_{modality_label}"
return (f_name1,
f_name2,
f_name3,
f_name4)
for i in range(0,len(name_list)):
name_list[i] += f"_{modality_label}"
return name_list
else:
modality_label = bids_name_dict[modality_type]['modality_label']
f_name1 += f"_{modality_label}"
f_name2 += f"_{modality_label}"
f_name3 += f"_{modality_label}"
f_name4 += f"_{modality_label}"
return (f_name1,
f_name2,
f_name3,
f_name4)
for i in range(0,len(name_list)):
name_list[i] += f"_{modality_label}"
return name_list

def source_to_bids(sub_data: SubDataInfo,
bids_name_dict: Dict,
Expand Down Expand Up @@ -1043,7 +1027,7 @@ def source_to_bids(sub_data: SubDataInfo,
out_dir=out_data_dir)

# Ensure that DWI and Fieldmap data are not kept as unknown
# by calling nifti_to_bids if that is the case.
# by calling nifti_to_bids if that is the case.
if img_data.bvals[0] and img_data.bvecs[0] and not modality_type:
modality_type = 'dwi'
modality_label = 'dwi'
Expand Down Expand Up @@ -1103,11 +1087,10 @@ def source_to_bids(sub_data: SubDataInfo,
pass
else:
modality_type: str = "unknown"

[bids_1, bids_2, bids_3, bids_4] = make_bids_name(bids_name_dict=bids_name_dict,
modality_type=modality_type)

bids_names: List[str] = [bids_1, bids_2, bids_3, bids_4]
bids_names: List[str] = make_bids_name(bids_name_dict=bids_name_dict,
modality_type=modality_type,
num_imgs=len(img_data.imgs))

# Image data return lists
imgs: List[str] = []
Expand Down Expand Up @@ -1394,11 +1377,10 @@ def nifti_to_bids(sub_data: SubDataInfo,
pass
else:
modality_type: str = "unknown"

[bids_1, bids_2, bids_3, bids_4] = make_bids_name(bids_name_dict=bids_name_dict,
modality_type=modality_type)

bids_names: List[str] = [bids_1, bids_2, bids_3, bids_4]

bids_names: List[str] = make_bids_name(bids_name_dict=bids_name_dict,
modality_type=modality_type,
num_imgs=len(img_data.imgs))

# Image data return lists
imgs: List[str] = []
Expand Down
3 changes: 3 additions & 0 deletions convert_source/cs_utils/bids_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ def construct_bids_dict(meta_dict: Optional[Dict] = None,

# Update BIDS ordered array
meta_list: List[str] = list(meta_dict.keys())
json_list: List[str] = list(json_dict.keys())

ordered_array.extend(json_list)
ordered_array.extend(meta_list)
ordered_list: List[str] = []

Expand Down
19 changes: 13 additions & 6 deletions convert_source/cs_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
import pydicom
import numpy as np

from collections import deque
from json import JSONDecodeError
from copy import deepcopy
from shutil import copy
from tqdm import tqdm

from collections import (
deque,
OrderedDict
)

from typing import (
List,
Dict,
Expand Down Expand Up @@ -572,7 +576,6 @@ def update_json(json_file: str,
Returns:
Updated JSON file.
"""

# Check if JSON file exists, if not, then create JSON file
if not os.path.exists(json_file):
with open(json_file,"w"): pass
Expand Down Expand Up @@ -618,11 +621,12 @@ def dict_multi_update(dictionary: Optional[Dict] = None,
if dictionary:
new_dict: Dict = deepcopy(dictionary)
else:
new_dict: Dict = {}
new_dict: OrderedDict = OrderedDict({})

for key,item in kwargs.items():
tmp_dict: Dict = {key:item}
new_dict.update(tmp_dict)
if (isinstance(item,int))or (isinstance(item,float)) or (isinstance(item,str)) or (isinstance(item,list)):
new_dict.update(tmp_dict)
return new_dict

def get_bvals(bval_file: Optional[str] = ""
Expand Down Expand Up @@ -1511,7 +1515,10 @@ def get_dcm_scan_tech(dcm_file: str,
ds = pydicom.dcmread(dcm_file)

# Search DICOM header for Scan Technique
dcm_scan_tech_str = str(ds[0x2001,0x1020])
try:
dcm_scan_tech_str: str = str(ds[0x2001,0x1020])
except KeyError:
dcm_scan_tech_str: str = ""

# Use dictionary to search in string
for i in search_arr:
Expand Down Expand Up @@ -1553,7 +1560,7 @@ def get_dcm_scan_tech(dcm_file: str,
dcm_fields: List[str] = ['SeriesDescription', 'ImageType', 'ProtocolName']

for dcm_field in dcm_fields:
dcm_scan_tech_str = str(eval(f"ds.{dcm_field}")) # This makes me dangerously uncomfortable
dcm_scan_tech_str: str = str(eval(f"ds.{dcm_field}")) # This makes me dangerously uncomfortable

# Use dictionary to search in string
for i in search_arr:
Expand Down
4 changes: 2 additions & 2 deletions convert_source/imgio/niio.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def get_data_params(file: str,
"TotalReadoutTime": tot_read_time,
"AcquisitionDuration": scan_time,
"SourceDataFormat": source_format})
elif 'par' in file.lower():
elif '.par' in file.lower():
wfs: float = get_wfs(file)
red_fact: float = par_red_fact(file)
mb: int = par_mb(file)
Expand All @@ -133,7 +133,7 @@ def get_data_params(file: str,
"EchoTime": echo_time,
"FlipAngle": flip_angle,
"SourceDataFormat": source_format})
elif 'nii' in file.lower():
elif '.nii' in file.lower():
tr: Union[float,str] = get_nii_tr(file)
source_format: str = "NIFTI"
tmp_dict.update({"RepetitionTime": tr,
Expand Down
29 changes: 23 additions & 6 deletions convert_source/imgio/pario.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def get_scan_time(par_file: str) -> Union[float,str]:
return scan_time

def get_echo_time(par_file: str,
tmp_dir: Optional[str] = None
tmp_dir: Optional[str] = None,
raise_exc: Optional[bool] = False
) -> float:
"""Reads the echo time (TE, in sec.) from a PAR header file.
Expand All @@ -167,9 +168,14 @@ def get_echo_time(par_file: str,
Arguments:
par_file: PAR header file.
tmp_dir: Path to temporary directory.
raise_exc: Raise exception in the case of multi-echo acquisitions.
Returns:
Echo time as a float.
Echo time as a float or ``None`` in the case of multi-echo data.
Raises:
PARfileReadError: Exception that is raised if multi-echo PAR files
are used as inputs.
"""
if tmp_dir:
pass
Expand All @@ -189,12 +195,16 @@ def get_echo_time(par_file: str,
_ = tmp.rm_tmp_dir(rm_parent=False)

if len(list(np.unique(mat[0:,16]))) > 1:
raise PARfileReadError(f"Two or more unique echo times were found in {par_file}. Please check.")
if raise_exc:
raise PARfileReadError(f"Two or more unique echo times were found in {par_file}. Please check.")
else:
return None
else:
return float(round(Decimal(float(np.unique(mat[0:,16]))/1000),4))

def get_flip_angle(par_file: str,
tmp_dir: Optional[str] = None
tmp_dir: Optional[str] = None,
raise_exc: Optional[bool] = False
) -> float:
"""Reads the flip angle (in degrees) from a PAR header file.
Expand All @@ -211,9 +221,13 @@ def get_flip_angle(par_file: str,
Arguments:
par_file: PAR header file.
tmp_dir: Path to temporary directory.
raise_exc: Raise exception in the case of multiple flip angles are found.
Returns:
Flip angle as a float.
Flip angle as a float or None if multiple flip angles are found.
Raises:
PARfileReadError: Exception that is raised if input PAR file contains multiple flip angles.
"""
if tmp_dir:
pass
Expand All @@ -233,6 +247,9 @@ def get_flip_angle(par_file: str,
_ = tmp.rm_tmp_dir(rm_parent=False)

if len(list(np.unique(mat[0:,21]))) > 1:
raise PARfileReadError(f"Two or more unique flip angles were found in {par_file}. Please check.")
if raise_exc:
raise PARfileReadError(f"Two or more unique flip angles were found in {par_file}. Please check.")
else:
return None
else:
return float(np.unique(mat[0:,21]))
2 changes: 1 addition & 1 deletion convert_source/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0a8
0.2.0a9

0 comments on commit e00d7a7

Please sign in to comment.