From 597789e1fe1ec0d986079f2684043bb9116b2b74 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Wed, 29 May 2024 21:24:17 -0400 Subject: [PATCH 1/6] 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 ec5639c6..2c34fde5 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 @@ -984,8 +988,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 3441c530499fe0d48fed60ee8a583461d617809c Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 30 May 2024 11:30:59 -0400 Subject: [PATCH 2/6] 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 2c34fde5..73569061 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -988,10 +988,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 d1eed3d0..cff062eb 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -111,6 +111,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 9a51164be3ad047448f853916ae93318128324ac Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 30 May 2024 15:38:37 -0400 Subject: [PATCH 3/6] 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 73569061..0b4fb1bf 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" @@ -989,9 +989,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 cff062eb..10b25955 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -114,7 +114,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 edffc36640b4dcb4dbc19e8a2b835b5a3c0f28cb Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 8 Aug 2024 14:57:32 -0400 Subject: [PATCH 4/6] 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 0b4fb1bf..1cb02cff 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] @@ -988,13 +1001,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 5894be71ea4b5ca4ace4c4a1bffffb4cd092ffa0 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 24 Sep 2024 14:19:56 -0400 Subject: [PATCH 5/6] fix --- heudiconv/convert.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 1cb02cff..dfc9102d 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -1001,16 +1001,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 From 9741ee845cd461767605e60b4dedd6c2f7024ea5 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Sat, 29 Mar 2025 10:03:02 -0400 Subject: [PATCH 6/6] fix M+MAGNITUDE case --- heudiconv/convert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index dfc9102d..4aa5b3da 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -1002,7 +1002,8 @@ def save_converted_files( len(set(filter(bool, channel_names))) > 1 ) # Check for uncombined data # Determine if data are complex (magnitude + phase or real + imag or all-4) - is_complex = len(set(IMAGETYPE_TO_PARTS.keys()).intersection(image_types)) + parts = filter(bool, [IMAGETYPE_TO_PARTS.get(it) for it in image_types]) + is_complex = len(set(parts)) > 1 echo_times_lst = sorted(echo_times) # also converts to list channel_names_lst = sorted(channel_names) # also converts to list