From 1d01c890a61f64982ef13c145906e93c22809cfd Mon Sep 17 00:00:00 2001 From: bpinsard Date: Wed, 29 May 2024 21:24:17 -0400 Subject: [PATCH 1/5] handles real+imaginary data --- heudiconv/convert.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index aecc70dc..69aada89 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -299,8 +299,8 @@ def prep_conversion( def update_complex_name(metadata: dict[str, Any], filename: str) -> str: """ - Insert `_part-` entity into filename if data are from a - sequence with magnitude/phase part. + Insert `_part-` entity into filename if data are from a + sequence with magnitude/phase/real/imaginary part. Parameters ---------- @@ -333,9 +333,13 @@ def update_complex_name(metadata: dict[str, Any], filename: str) -> str: # Check to see if it is magnitude or phase part: img_type = cast(List[str], metadata.get("ImageType", [])) if "M" in img_type: - mag_or_phase = "mag" + part = "mag" elif "P" in img_type: - mag_or_phase = "phase" + part = "phase" + elif "REAL" in img_type: + part = "real" + elif "IMAGINARY" in img_type: + part = "imag" else: raise RuntimeError("Data type could not be inferred from the metadata.") @@ -343,7 +347,7 @@ def update_complex_name(metadata: dict[str, Any], filename: str) -> str: filetype = "_" + filename.split("_")[-1] # Insert part label - if not ("_part-%s" % mag_or_phase) in filename: + if not ("_part-%s" % part) in filename: # If "_part-" is specified, prepend the 'mag_or_phase' value. if "_part-" in filename: raise BIDSError( @@ -368,7 +372,7 @@ def update_complex_name(metadata: dict[str, Any], filename: str) -> str: ] for label in entities_after_part: if (label == filetype) or (label in filename): - filename = filename.replace(label, "_part-%s%s" % (mag_or_phase, label)) + filename = filename.replace(label, "_part-%s%s" % (part, label)) break return filename @@ -975,8 +979,9 @@ def save_converted_files( is_uncombined = ( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data + PARTS = ["M", "P", "IMAGINARY", "REAL"] is_complex = ( - "M" in image_types and "P" in image_types + len(set(filter(lambda x: [part in x for part in PARTS], image_types))) > 1 ) # Determine if data are complex (magnitude + phase) echo_times_lst = sorted(echo_times) # also converts to list channel_names_lst = sorted(channel_names) # also converts to list From d2599e66a05bed7110270b4a26c56698c75836f5 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 30 May 2024 11:30:59 -0400 Subject: [PATCH 2/5] add complex part to ImageType for use in heuristics, it is done in dcm2niix when converting --- heudiconv/convert.py | 8 ++++---- heudiconv/dicoms.py | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 69aada89..9bc6e0b7 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -979,10 +979,10 @@ def save_converted_files( is_uncombined = ( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data - PARTS = ["M", "P", "IMAGINARY", "REAL"] - is_complex = ( - len(set(filter(lambda x: [part in x for part in PARTS], image_types))) > 1 - ) # Determine if data are complex (magnitude + phase) + CPLX_PARTS = ["MAGNITUDE", "PHASE", "IMAGINARY", "REAL"] + is_complex = len(set([ + it for its in image_types for part in CPLX_PARTS if 'part' + ])) # Determine if data are complex (magnitude + phase or real + imag or all-4) echo_times_lst = sorted(echo_times) # also converts to list channel_names_lst = sorted(channel_names) # also converts to list diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index 6210f83d..736dedfa 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -106,6 +106,15 @@ def create_seqinfo( else: sequence_name = "" + # GE data + # see https://github.com/rordenlab/dcm2niix/tree/master/GE#complex-image-component + if dcminfo.get([0x43, 0x102F]): + GE_CPLX_CODING = ["PHASE", "MAGNITUDE", "REAL", "IMAGINARY"] + cplx_idx = int(dcminfo.get([0x43, 0x102F]).value) + part = GE_CPLX_CODING[cplx_idx] + if part not in image_type: + image_type = image_type + (part,) + # initialized in `group_dicoms_to_seqinfos` global total_files total_files += len(series_files) From 8a5d27c8c571142bfd66dc61e917033239516165 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 30 May 2024 15:38:37 -0400 Subject: [PATCH 3/5] more fixes, works on example GE data, require dcm2niix patch --- heudiconv/convert.py | 17 ++++++++++++----- heudiconv/dicoms.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 9bc6e0b7..b48e26fa 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -332,9 +332,9 @@ def update_complex_name(metadata: dict[str, Any], filename: str) -> str: # Check to see if it is magnitude or phase part: img_type = cast(List[str], metadata.get("ImageType", [])) - if "M" in img_type: + if "M" in img_type or "MAGNITUDE" in img_type: part = "mag" - elif "P" in img_type: + elif "P" in img_type or "PHASE" in img_type: part = "phase" elif "REAL" in img_type: part = "real" @@ -980,9 +980,16 @@ def save_converted_files( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data CPLX_PARTS = ["MAGNITUDE", "PHASE", "IMAGINARY", "REAL"] - is_complex = len(set([ - it for its in image_types for part in CPLX_PARTS if 'part' - ])) # Determine if data are complex (magnitude + phase or real + imag or all-4) + is_complex = len( + set( + [ + part + for its in image_types + for part in CPLX_PARTS + if part in its or part[0] in its + ] + ) + ) # Determine if data are complex (magnitude + phase or real + imag or all-4) echo_times_lst = sorted(echo_times) # also converts to list channel_names_lst = sorted(channel_names) # also converts to list diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index 736dedfa..03c99d52 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -109,7 +109,7 @@ def create_seqinfo( # GE data # see https://github.com/rordenlab/dcm2niix/tree/master/GE#complex-image-component if dcminfo.get([0x43, 0x102F]): - GE_CPLX_CODING = ["PHASE", "MAGNITUDE", "REAL", "IMAGINARY"] + GE_CPLX_CODING = ["MAGNITUDE", "PHASE", "REAL", "IMAGINARY"] cplx_idx = int(dcminfo.get([0x43, 0x102F]).value) part = GE_CPLX_CODING[cplx_idx] if part not in image_type: From 44544e41165ba97f209fc253cea8d4b851e6ed76 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 8 Aug 2024 14:57:32 -0400 Subject: [PATCH 4/5] add/fit-in suggestions --- heudiconv/convert.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index b48e26fa..55ebd95c 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -297,6 +297,16 @@ def prep_conversion( ) +IMAGETYPE_TO_PARTS = { + "M": "mag", + "MAGNITUDE": "mag", + "P": "phase", + "PHASE": "phase", + "REAL": "real", + "IMAGINARY": "imag", +} + + def update_complex_name(metadata: dict[str, Any], filename: str) -> str: """ Insert `_part-` entity into filename if data are from a @@ -330,18 +340,21 @@ def update_complex_name(metadata: dict[str, Any], filename: str) -> str: if any(ut in filename for ut in unsupported_types): return filename - # Check to see if it is magnitude or phase part: img_type = cast(List[str], metadata.get("ImageType", [])) - if "M" in img_type or "MAGNITUDE" in img_type: - part = "mag" - elif "P" in img_type or "PHASE" in img_type: - part = "phase" - elif "REAL" in img_type: - part = "real" - elif "IMAGINARY" in img_type: - part = "imag" + + present_parts = set( + IMAGETYPE_TO_PARTS[tp] for tp in img_type if tp in IMAGETYPE_TO_PARTS + ) + if not present_parts: + raise RuntimeError( + f"Data type could not be inferred from the ImageType={img_type}. Known types are: {sorted(IMAGETYPE_TO_PARTS)}" + ) + elif len(present_parts) == 1: + part = present_parts.pop() else: - raise RuntimeError("Data type could not be inferred from the metadata.") + raise RuntimeError( + f"Data type could not be inferred from the ImageType={img_type}. Multiple BIDS parts matched: {present_parts}" + ) # Determine scan suffix filetype = "_" + filename.split("_")[-1] @@ -979,13 +992,12 @@ def save_converted_files( is_uncombined = ( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data - CPLX_PARTS = ["MAGNITUDE", "PHASE", "IMAGINARY", "REAL"] is_complex = len( set( [ part for its in image_types - for part in CPLX_PARTS + for part in IMAGETYPE_TO_PARTS.keys() if part in its or part[0] in its ] ) From 152fc2d735406a736aed8e020c33f6123b25981b Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 24 Sep 2024 14:19:56 -0400 Subject: [PATCH 5/5] fix --- heudiconv/convert.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 55ebd95c..6a67e32c 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -992,16 +992,8 @@ def save_converted_files( is_uncombined = ( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data - is_complex = len( - set( - [ - part - for its in image_types - for part in IMAGETYPE_TO_PARTS.keys() - if part in its or part[0] in its - ] - ) - ) # Determine if data are complex (magnitude + phase or real + imag or all-4) + # Determine if data are complex (magnitude + phase or real + imag or all-4) + is_complex = len(set(IMAGETYPE_TO_PARTS.keys()).intersection(image_types)) echo_times_lst = sorted(echo_times) # also converts to list channel_names_lst = sorted(channel_names) # also converts to list