From 6c4f90088ee46fe33a480dedb75d25390ce428e3 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 29 Nov 2024 01:59:27 +0200 Subject: [PATCH 01/14] [Bug] Minor fix to headers on installation rst file --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 82b9555b..ff4510cc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -30,7 +30,7 @@ You can build the package by running the following command: :: $ docker build -t cbica/nichart_dlmuse . -.. _`Singularity/Apptainer build` +.. _`Singularity/Apptainer build`: Singularity and Apptainer images can be built for NiChart_DLMUSE, allowing for frozen versions of the pipeline and easier installation for end-users. Note that the Singularity project recently underwent a rename to "Apptainer", with a commercial @@ -43,7 +43,7 @@ After installing the container engine, run: :: This will take some time, but will build a containerized version of your current repo. Be aware that this includes any local changes! The nichart_dlmuse.sif file can be distributed via direct download, or pushed to a container registry that accepts SIF images. -.. _`Manual installation` +.. _`Manual installation`: You can manually build the package from source by running: :: From 331cdef2e86191857b9413ede78e60db30cd251b Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 29 Nov 2024 02:01:43 +0200 Subject: [PATCH 02/14] [Update] Make installing recommendation a note instead of plain text --- docs/installation.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ff4510cc..8e0c2cd0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -51,5 +51,6 @@ You can manually build the package from source by running: :: $ cd NiChart_DLMUSE && python3 -m pip install -e . -We **do not** recomment installing the package directly from source as the repository above is under heavy development and can cause -crashes and bugs. +.. note:: + We **do not** recomment installing the package directly from source as the repository above is under heavy development and can cause + crashes and bugs. From c0260aecf32d354b57b40cf8e6fa255aa901784c Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 29 Nov 2024 02:02:24 +0200 Subject: [PATCH 03/14] [Update] Fix syntax error --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8e0c2cd0..ce3d0747 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -52,5 +52,5 @@ You can manually build the package from source by running: :: $ cd NiChart_DLMUSE && python3 -m pip install -e . .. note:: - We **do not** recomment installing the package directly from source as the repository above is under heavy development and can cause + We **do not** recommend installing the package directly from source as the repository above is under heavy development and can cause crashes and bugs. From 1ef43fcb1fd788788457655cfe0b9685a7ac0b9c Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 29 Nov 2024 02:09:16 +0200 Subject: [PATCH 04/14] [Update] Fix headers again(it was another issue, i forgot the correct syntax) --- docs/installation.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ce3d0747..da6f4a1e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -30,7 +30,9 @@ You can build the package by running the following command: :: $ docker build -t cbica/nichart_dlmuse . -.. _`Singularity/Apptainer build`: +*************************** +Singularity/Apptainer build +*************************** Singularity and Apptainer images can be built for NiChart_DLMUSE, allowing for frozen versions of the pipeline and easier installation for end-users. Note that the Singularity project recently underwent a rename to "Apptainer", with a commercial @@ -43,7 +45,9 @@ After installing the container engine, run: :: This will take some time, but will build a containerized version of your current repo. Be aware that this includes any local changes! The nichart_dlmuse.sif file can be distributed via direct download, or pushed to a container registry that accepts SIF images. -.. _`Manual installation`: +******************* +Manual installation +******************* You can manually build the package from source by running: :: From bb8bde87b86b096211ced9c2700c1469964106e8 Mon Sep 17 00:00:00 2001 From: Alexander Getka <59709326+AlexanderGetka-cbica@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:39:11 -0600 Subject: [PATCH 05/14] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b353a48a..9698ec15 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="NiChart_DLMUSE", - version="1.0.8", + version="1.0.9", description="Run NiChart_DLMUSE on your data (currently only structural pipeline is supported).", long_description=long_description, long_description_content_type="text/markdown", From 976fa36b55bfebee8fc4a7f2880928bbad1f4afc Mon Sep 17 00:00:00 2001 From: Alexander Getka <59709326+AlexanderGetka-cbica@users.noreply.github.com> Date: Mon, 9 Dec 2024 04:29:58 -0500 Subject: [PATCH 06/14] Update __init__.py --- NiChart_DLMUSE/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/NiChart_DLMUSE/__init__.py b/NiChart_DLMUSE/__init__.py index 7f41d576..aa827483 100644 --- a/NiChart_DLMUSE/__init__.py +++ b/NiChart_DLMUSE/__init__.py @@ -1,4 +1 @@ from .dlmuse_pipeline import run_pipeline, run_dlicv, run_dlmuse - -if __name__ == "__main__": - pass From 99e4a7edfc113b99700f6fa66bd6063e19de172d Mon Sep 17 00:00:00 2001 From: Alexander Getka <59709326+AlexanderGetka-cbica@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:20:06 -0500 Subject: [PATCH 07/14] Update README.rst --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d7b23f9f..b458232f 100644 --- a/README.rst +++ b/README.rst @@ -53,11 +53,11 @@ Or from out latest stable PyPI wheel: :: (If needed for your system) Install PyTorch with compatible CUDA. You only need to run this step if you experience errors with CUDA while running NiChart_DLMUSE. Run "pip uninstall torch torchaudio torchvision". -Then follow the `PyTorch installation instructions `_ for your CUDA version. -Note that we highly recommend matching the torch version you install to the version used by NiChart_DLMUSE. For example, after installing NiChart_DLMUSE: :: +Then follow the `PyTorch installation instructions `_ for your CUDA version. +Specific versions are needed for full compatibility. On Linux, download Torch 2.3.1, on Windows install 2.5.1. For example, after installing NiChart_DLMUSE: :: $ pip uninstall torch torchvision torchaudio - pip install torch==2.2.1 --index-url https://download.pytorch.org/whl/cu121 + pip install torch==2.3.1 --index-url https://download.pytorch.org/whl/cu121 ****************** Run NiChart_DLMUSE @@ -77,7 +77,7 @@ Docker build ************ The package comes already pre-built as a `docker container `_, for convenience. Please see `Usage <#usage>`_ for more information on how to use it. Alternatively, you can build the docker image -locally, like so: :: +locally from the source repo, like so: :: $ docker build -t cbica/nichart_dlmuse . From 138ac339c315b4ec8c1ead221ec316df08f6054e Mon Sep 17 00:00:00 2001 From: Alexander Getka Date: Wed, 18 Jun 2025 21:09:17 -0400 Subject: [PATCH 08/14] add simple wrapper script --- scripts/wrapper.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scripts/wrapper.py diff --git a/scripts/wrapper.py b/scripts/wrapper.py new file mode 100644 index 00000000..9f96890f --- /dev/null +++ b/scripts/wrapper.py @@ -0,0 +1,44 @@ +import argparse +import shutil +import subprocess +import tempfile +from pathlib import Path + +# This wrapper script just adapts NiChart_DLMUSE to take two separate output args. Everything else is passed transparently + +def main(): + parser = argparse.ArgumentParser(description="Wrapper", allow_abbrev=False) + parser.add_argument("-i", "--in_dir", required=True, help="Input directory") + parser.add_argument("-o1", "--out_segs", required=True, help="Output directory for segmentation files") + parser.add_argument("-o2", "--out_csv", required=True, help="Output directory for CSV files") + + # Parse known args; leave the rest for original app + args, extra_args = parser.parse_known_args() + + input_dir = args.i + seg_dir = Path(args.o1) + csv_dir = Path(args.o2) + + seg_dir.mkdir(parents=True, exist_ok=True) + csv_dir.mkdir(parents=True, exist_ok=True) + + with tempfile.TemporaryDirectory() as tmp_output: + tmp_output_path = Path(tmp_output) + + # Build command to run original application + cmd = ["NiChart_DLMUSE", "-i", input_dir, "-o", str(tmp_output_path)] + extra_args + + subprocess.run(cmd, check=True) + + # Copy output files + for item in tmp_output_path.rglob("*"): + if item.is_file(): + if item.suffix.lower() == ".csv": + shutil.copy2(item, csv_dir / item.name) + else: + dest_path = seg_dir / item.relative_to(tmp_output_path) + dest_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(item, dest_path) + +if __name__ == "__main__": + main() From 7ceae29af59edfc78059dd81b3e27987126a0714 Mon Sep 17 00:00:00 2001 From: Alexander Getka Date: Wed, 18 Jun 2025 21:12:08 -0400 Subject: [PATCH 09/14] minor fix in wrapper --- scripts/wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wrapper.py b/scripts/wrapper.py index 9f96890f..0bc28920 100644 --- a/scripts/wrapper.py +++ b/scripts/wrapper.py @@ -10,7 +10,7 @@ def main(): parser = argparse.ArgumentParser(description="Wrapper", allow_abbrev=False) parser.add_argument("-i", "--in_dir", required=True, help="Input directory") parser.add_argument("-o1", "--out_segs", required=True, help="Output directory for segmentation files") - parser.add_argument("-o2", "--out_csv", required=True, help="Output directory for CSV files") + parser.add_argument("-o2", "--out_csvs", required=True, help="Output directory for CSV files") # Parse known args; leave the rest for original app args, extra_args = parser.parse_known_args() From 675f2a9aa668a5f81c4b4dfc2692e9ad99a6e4c4 Mon Sep 17 00:00:00 2001 From: Alexander Getka Date: Wed, 18 Jun 2025 22:59:55 -0400 Subject: [PATCH 10/14] argparse fix --- scripts/wrapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/wrapper.py b/scripts/wrapper.py index 0bc28920..540744b6 100644 --- a/scripts/wrapper.py +++ b/scripts/wrapper.py @@ -15,9 +15,9 @@ def main(): # Parse known args; leave the rest for original app args, extra_args = parser.parse_known_args() - input_dir = args.i - seg_dir = Path(args.o1) - csv_dir = Path(args.o2) + input_dir = args.in_dir + seg_dir = Path(args.out_segs) + csv_dir = Path(args.out_csvs) seg_dir.mkdir(parents=True, exist_ok=True) csv_dir.mkdir(parents=True, exist_ok=True) From 7cd3587c1e017e3516c4aac96f4c8833e5359537 Mon Sep 17 00:00:00 2001 From: Alexander Getka Date: Thu, 19 Jun 2025 01:48:29 -0400 Subject: [PATCH 11/14] wrapper fix --- scripts/wrapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/wrapper.py b/scripts/wrapper.py index 540744b6..44aa4018 100644 --- a/scripts/wrapper.py +++ b/scripts/wrapper.py @@ -1,6 +1,6 @@ import argparse import shutil -import subprocess +import os import tempfile from pathlib import Path @@ -27,8 +27,8 @@ def main(): # Build command to run original application cmd = ["NiChart_DLMUSE", "-i", input_dir, "-o", str(tmp_output_path)] + extra_args - - subprocess.run(cmd, check=True) + command = ' '.join(cmd) + os.system(command) # Copy output files for item in tmp_output_path.rglob("*"): From 81ee7870b55deb29635429ae6b302929f7afa69a Mon Sep 17 00:00:00 2001 From: Alexander Getka Date: Thu, 19 Jun 2025 18:43:23 -0400 Subject: [PATCH 12/14] logging fix --- NiChart_DLMUSE/dlmuse_pipeline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NiChart_DLMUSE/dlmuse_pipeline.py b/NiChart_DLMUSE/dlmuse_pipeline.py index 8268fd79..25733f67 100644 --- a/NiChart_DLMUSE/dlmuse_pipeline.py +++ b/NiChart_DLMUSE/dlmuse_pipeline.py @@ -1,5 +1,6 @@ import logging import os +import sys import pkg_resources # type: ignore @@ -32,7 +33,9 @@ ) logger = logging.getLogger(__name__) -logging.basicConfig(filename="pipeline.log", encoding="utf-8", level=logging.DEBUG) +logging.basicConfig(encoding="utf-8", level=logging.DEBUG, + handlers=[logging.FileHandler("pipeline.log"), + logging.StreamHandler(sys.stdout)]) def run_pipeline( From bd2942aab9f75b7d71aa520b3dbcb24bb88d1e36 Mon Sep 17 00:00:00 2001 From: Alexander Getka <59709326+AlexanderGetka-cbica@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:10:55 -0400 Subject: [PATCH 13/14] Update __main__.py --- NiChart_DLMUSE/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NiChart_DLMUSE/__main__.py b/NiChart_DLMUSE/__main__.py index 026353a0..f939c690 100644 --- a/NiChart_DLMUSE/__main__.py +++ b/NiChart_DLMUSE/__main__.py @@ -87,7 +87,7 @@ def main() -> None: "--cores", type=str, help="Number of cores", - default=4, + default=1, required=False, ) From e41da5edc9f6ef00f1a6b80d91da7c5b5bbde98f Mon Sep 17 00:00:00 2001 From: Tien Tong Date: Wed, 3 Sep 2025 14:23:42 -0400 Subject: [PATCH 14/14] add sitk.ConnectedComponent for refaced data --- NiChart_DLMUSE/__init__.py | 2 +- NiChart_DLMUSE/__main__.py | 29 +++++++++++++++++++++++++++-- NiChart_DLMUSE/dlmuse_pipeline.py | 19 +++++++++++++++++++ NiChart_DLMUSE/utils.py | 4 +++- requirements.txt | 1 + scripts/wrapper.py | 25 ++++++++++++++++++++----- 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/NiChart_DLMUSE/__init__.py b/NiChart_DLMUSE/__init__.py index aa827483..7612d577 100644 --- a/NiChart_DLMUSE/__init__.py +++ b/NiChart_DLMUSE/__init__.py @@ -1 +1 @@ -from .dlmuse_pipeline import run_pipeline, run_dlicv, run_dlmuse +from .dlmuse_pipeline import run_dlicv, run_dlmuse, run_pipeline diff --git a/NiChart_DLMUSE/__main__.py b/NiChart_DLMUSE/__main__.py index f939c690..f879750e 100644 --- a/NiChart_DLMUSE/__main__.py +++ b/NiChart_DLMUSE/__main__.py @@ -131,6 +131,14 @@ def main() -> None: help="Pass additional args to be sent to DLICV (ex. '-nps 1 -npp 1'). It is recommended to surround these args in a set of double quotes. See the DLICV documentation for details.", ) + parser.add_argument( + "--refaced-data", + action="store_true", + required=False, + default=False, + help="If set, refine DLICV mask by keeping only the largest connected component (for refaced data).", + ) + # HELP argument help = "Show this message and exit" parser.add_argument("-h", "--help", action="store_true", help=help) @@ -142,6 +150,7 @@ def main() -> None: device = args.device dlicv_extra_args = args.dlicv_args dlmuse_extra_args = args.dlmuse_args + refaced_data = args.refaced_data print() print("Arguments:") @@ -185,6 +194,7 @@ def main() -> None: device, dlmuse_extra_args, dlicv_extra_args, + refaced_data, i, ), ) @@ -198,7 +208,14 @@ def main() -> None: remove_subfolders("raw_temp_T1") remove_subfolders(out_dir) else: # No core parallelization - run_pipeline(in_dir, out_dir, device, dlmuse_extra_args, dlicv_extra_args) + run_pipeline( + in_dir, + out_dir, + device, + dlmuse_extra_args, + dlicv_extra_args, + refaced_data, + ) else: # Non-BIDS if int(args.cores) > 1: @@ -216,6 +233,7 @@ def main() -> None: device, dlmuse_extra_args, dlicv_extra_args, + refaced_data, i, ), ) @@ -229,7 +247,14 @@ def main() -> None: remove_subfolders(in_dir) remove_subfolders(out_dir) else: # No core parallelization - run_pipeline(in_dir, out_dir, device, dlmuse_extra_args, dlicv_extra_args) + run_pipeline( + in_dir, + out_dir, + device, + dlmuse_extra_args, + dlicv_extra_args, + refaced_data, + ) if __name__ == "__main__": diff --git a/NiChart_DLMUSE/dlmuse_pipeline.py b/NiChart_DLMUSE/dlmuse_pipeline.py index 25733f67..c6642d89 100644 --- a/NiChart_DLMUSE/dlmuse_pipeline.py +++ b/NiChart_DLMUSE/dlmuse_pipeline.py @@ -44,6 +44,7 @@ def run_pipeline( device: str, dlmuse_extra_args: str = '', dlicv_extra_args: str = '', + refaced_data: bool = False, sub_fldr: int = 1, progress_bar = None, ) -> None: @@ -111,6 +112,24 @@ def run_pipeline( progress_bar.set_description("Running DLICV") run_dlicv(in_dir, in_suff, out_dir, out_suff, device, dlicv_extra_args) + # If refaced data is specified, refine the masks used in the next step (s3_masked) + if refaced_data: + import SimpleITK as sitk + + for _, tmp_row in df_img.iterrows(): + img_prefix = tmp_row.img_prefix + fpath = os.path.join(out_dir, img_prefix + SUFF_DLICV) + if os.path.exists(fpath): + s2_dlicv_output = sitk.ReadImage(fpath) + # Keep only the largest connected component + mask_component = sitk.ConnectedComponent(s2_dlicv_output) + mask_sorted_component = sitk.RelabelComponent( + mask_component, sortByObjectSize=True + ) + final_mask = sitk.Equal(mask_sorted_component, 1) + # Write refined mask back in-place within s2_dlicv + sitk.WriteImage(final_mask, fpath) + logging.info(f"Applying DLICV for batch [{sub_fldr}] done") logging.info(f"Applying mask for batch [{sub_fldr}]...") diff --git a/NiChart_DLMUSE/utils.py b/NiChart_DLMUSE/utils.py index f4c4e310..60966f66 100644 --- a/NiChart_DLMUSE/utils.py +++ b/NiChart_DLMUSE/utils.py @@ -65,7 +65,9 @@ def remove_common_suffix(list_files: list) -> list: bnames = list_files if len(list_files) == 1: - if list_files[0].endswith('_T1'): # If there is a single image with suffix _T1, remove it + if list_files[0].endswith( + "_T1" + ): # If there is a single image with suffix _T1, remove it bnames = [x[0:-3] for x in bnames] return bnames diff --git a/requirements.txt b/requirements.txt index 79414b1a..2428040f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ DLICV DLMUSE nibabel>=5.2 scipy +SimpleITK # Developer tools pytest diff --git a/scripts/wrapper.py b/scripts/wrapper.py index 44aa4018..254175bc 100644 --- a/scripts/wrapper.py +++ b/scripts/wrapper.py @@ -1,16 +1,24 @@ import argparse -import shutil import os +import shutil import tempfile from pathlib import Path # This wrapper script just adapts NiChart_DLMUSE to take two separate output args. Everything else is passed transparently + def main(): parser = argparse.ArgumentParser(description="Wrapper", allow_abbrev=False) parser.add_argument("-i", "--in_dir", required=True, help="Input directory") - parser.add_argument("-o1", "--out_segs", required=True, help="Output directory for segmentation files") - parser.add_argument("-o2", "--out_csvs", required=True, help="Output directory for CSV files") + parser.add_argument( + "-o1", + "--out_segs", + required=True, + help="Output directory for segmentation files", + ) + parser.add_argument( + "-o2", "--out_csvs", required=True, help="Output directory for CSV files" + ) # Parse known args; leave the rest for original app args, extra_args = parser.parse_known_args() @@ -26,8 +34,14 @@ def main(): tmp_output_path = Path(tmp_output) # Build command to run original application - cmd = ["NiChart_DLMUSE", "-i", input_dir, "-o", str(tmp_output_path)] + extra_args - command = ' '.join(cmd) + cmd = [ + "NiChart_DLMUSE", + "-i", + input_dir, + "-o", + str(tmp_output_path), + ] + extra_args + command = " ".join(cmd) os.system(command) # Copy output files @@ -40,5 +54,6 @@ def main(): dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(item, dest_path) + if __name__ == "__main__": main()