Repository navigation
feat: add IVIM workflow DAG with MinIO integration and platform docum… #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
96133a6
55d54c4
8ceb5c1
94327b4
8372c92
b185bf8
accb817
04b86ed
04396bf
fedad5a
c450fd1
c75496a
fcd0649
10f9c57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| #!/usr/bin/env python3 | ||
| import os | ||
| import glob | ||
| import json | ||
| import nibabel as nib | ||
| import shutil | ||
| import numpy as np | ||
| from tqdm import tqdm | ||
| from src.wrappers.OsipiBase import OsipiBase | ||
|
|
||
| # --------------------- Helper Functions --------------------- # | ||
|
|
||
| def read_nifti_file(input_file): | ||
| nifti_img = nib.load(input_file) | ||
| return nifti_img.get_fdata(), nifti_img.affine, nifti_img.header | ||
|
|
||
| def read_bval_file(bval_file): | ||
| return np.genfromtxt(bval_file, dtype=float) | ||
|
|
||
| def read_bvec_file(bvec_file): | ||
| bvec_data = np.genfromtxt(bvec_file) | ||
| return np.transpose(bvec_data) | ||
|
|
||
| def save_nifti_file(data, affine, output_file): | ||
| output_img = nib.Nifti1Image(data, affine) | ||
| nib.save(output_img, output_file) | ||
|
|
||
| def loop_over_first_n_minus_1_dimensions(arr): | ||
| n = arr.ndim | ||
| for idx in np.ndindex(*arr.shape[:n - 1]): | ||
| yield idx, arr[idx].flatten() | ||
|
|
||
| def load_config(): | ||
| workflow_dir = os.environ["WORKFLOW_DIR"] | ||
| config_path = os.path.join(workflow_dir, "conf", "conf.json") | ||
| with open(config_path, "r") as f: | ||
| return json.load(f) | ||
|
|
||
| # --------------------- Main Execution --------------------- # | ||
| if __name__ == "__main__": | ||
| # Load config | ||
| config = load_config() | ||
| config = config["workflow_form"] | ||
|
|
||
| # Kaapana environment variables | ||
| WORKFLOW_DIR = os.environ["WORKFLOW_DIR"] | ||
| OPERATOR_IN_DIR = os.environ["OPERATOR_IN_DIR"] | ||
| OPERATOR_OUT_DIR = os.environ["OPERATOR_OUT_DIR"] | ||
|
|
||
| element_input_dir = os.path.join(WORKFLOW_DIR, OPERATOR_IN_DIR) | ||
| element_output_dir = os.path.join(WORKFLOW_DIR, OPERATOR_OUT_DIR) | ||
| os.makedirs(element_output_dir, exist_ok=True) | ||
|
|
||
| # Initialize input_file, bvec_file, bval_file to None | ||
| input_file = None | ||
| bvec_file = None | ||
| bval_file = None | ||
|
|
||
| # Check upload type | ||
| dicom_or_nifti = config.get("upload_type", "nifti").lower() | ||
|
|
||
| if dicom_or_nifti == "nifti": | ||
| # Parse and validate source files | ||
| source_files_kaapana = [f.strip() for f in config.get("source_files").split(",")] | ||
| input_file, bvec_file, bval_file = source_files_kaapana | ||
|
|
||
| # Detect by extension | ||
| input_file = next((f for f in source_files_kaapana if f.endswith((".nii", ".nii.gz"))), None) | ||
| bvec_file = next((f for f in source_files_kaapana if f.endswith(".bvec")), None) | ||
| bval_file = next((f for f in source_files_kaapana if f.endswith(".bval")), None) | ||
|
|
||
| # Validate presence | ||
| if not input_file or not bvec_file or not bval_file: | ||
| raise ValueError( | ||
| f"Expected files to include one NIfTI (.nii/.nii.gz), one .bvec, and one .bval, " | ||
| f"but got: {source_files_kaapana}" | ||
| ) | ||
|
|
||
| input_file = os.path.join(element_input_dir, input_file) | ||
| bvec_file = os.path.join(element_input_dir, bvec_file) | ||
| bval_file = os.path.join(element_input_dir, bval_file) | ||
|
|
||
| else: | ||
| # DICOM case → follow batch structure | ||
| BATCH_DIR = os.path.join(WORKFLOW_DIR, "batch") | ||
| batch_folders = sorted([f for f in glob.glob(os.path.join(BATCH_DIR, "*"))]) | ||
| print(f"batch-folders - {batch_folders}") | ||
|
|
||
| if not batch_folders: | ||
| raise FileNotFoundError(f"No batch folders found in {BATCH_DIR}") | ||
|
|
||
| # Pick the first batch folder (usually one per patient/series) | ||
| batch_input_dir = os.path.join(batch_folders[0], "dicom_to_nifti") | ||
|
|
||
| nifti_files = sorted(glob.glob(os.path.join(batch_input_dir, "*.nii.gz"))) | ||
| if not nifti_files: | ||
| raise FileNotFoundError(f"No NIfTI files found in {batch_input_dir}") | ||
|
|
||
| paired_files = [] | ||
| for nifti in nifti_files: | ||
| base = os.path.splitext(os.path.splitext(os.path.basename(nifti))[0])[0] # remove .nii.gz | ||
| bvec_file = os.path.join(batch_input_dir, f"{base}.bvec") | ||
| bval_file = os.path.join(batch_input_dir, f"{base}.bval") | ||
| if os.path.exists(bvec_file) and os.path.exists(bval_file): | ||
| paired_files.append((nifti, bvec_file, bval_file)) | ||
| else: | ||
| raise FileNotFoundError(f"Missing bvec/bval for {nifti}") | ||
|
|
||
| # Use the first matched trio | ||
| input_file, bvec_file, bval_file = paired_files[0] | ||
|
|
||
| print(f"Using DICOM-converted files from {batch_input_dir}:\n" | ||
| f" {input_file}\n {bvec_file}\n {bval_file}") | ||
|
|
||
| # Optional config values | ||
| affine_override = config.get("affine", None) | ||
| algorithm = config.get("algorithm", "OJ_GU_seg") | ||
| algorithm_args = config.get("algorithm_args", None) | ||
|
|
||
| # Load input data | ||
| data, affine, _ = read_nifti_file(input_file) | ||
| bvecs = read_bvec_file(bvec_file) | ||
| bvals = read_bval_file(bval_file) | ||
|
|
||
| # Override affine if provided | ||
| if affine_override: | ||
| affine = np.array(affine_override).reshape(4, 4) | ||
|
|
||
| # Initialize model | ||
| fit = OsipiBase(algorithm=algorithm) | ||
|
|
||
| # Preallocate output arrays | ||
| shape = data.shape[:data.ndim - 1] | ||
| f_image = np.zeros(shape, dtype=np.float32) | ||
| Dp_image = np.zeros(shape, dtype=np.float32) | ||
| D_image = np.zeros(shape, dtype=np.float32) | ||
|
|
||
| total_iteration = np.prod(shape) | ||
|
|
||
| # Fit IVIM model voxel by voxel | ||
| for idx, view in tqdm( | ||
| loop_over_first_n_minus_1_dimensions(data), | ||
| desc="Fitting IVIM model", dynamic_ncols=True, total=total_iteration | ||
| ): | ||
| fit_result = fit.osipi_fit(view, bvals) | ||
| f_image[idx] = fit_result["f"] | ||
| Dp_image[idx] = fit_result["Dp"] | ||
| D_image[idx] = fit_result["D"] | ||
|
|
||
| # Save outputs | ||
| save_nifti_file(f_image, affine, os.path.join(element_output_dir, "f.nii.gz")) | ||
| save_nifti_file(Dp_image, affine, os.path.join(element_output_dir, "dp.nii.gz")) | ||
| save_nifti_file(D_image, affine, os.path.join(element_output_dir, "d.nii.gz")) | ||
|
|
||
| # Copy all .nii.gz from input directory to WORKFLOW_DIR (Kaapana workaround) | ||
| nii_files = glob.glob(os.path.join(element_input_dir, "*.nii.gz")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about just saving the output files to this folder directly?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Due to the sources file bug, the minio put operator is looking for the nifti input fil in the WORKFLOW_DIR that is why I am copying it there. The output file is expected to be saved in the element_output_dir, that is the convension.The above was a workaround due to the bug in kaapana.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand. The input files are already in the input folder so it's just about moving the new files to the correct folder? |
||
| for nii_file in nii_files: | ||
| shutil.copy(nii_file, WORKFLOW_DIR) | ||
| print(f"Copied {nii_file} to {WORKFLOW_DIR}") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # Kaapana Installation Guide | ||
|
|
||
| ## Overview | ||
|
|
||
| The Kaapana installation process consists of three main steps: | ||
|
|
||
| 1. **Building** all the container images required for Kaapana and pushing them to a remote registry, local registry, or local tarball | ||
| 2. **Installation** of Kaapana after building the images | ||
| 3. **Deployment** of Kaapana after installation | ||
|
|
||
| For my setup, I used the remote registry approach, which is also the recommended format. | ||
|
|
||
| ## Machine Requirements | ||
|
|
||
| Refer to the Kaapana Hardware and Network Requirement Docs: | ||
| https://kaapana.readthedocs.io/en/stable/installation_guide/requirements.html#requirements | ||
|
|
||
| ## Step 1: Building Container Images | ||
|
|
||
| ### Registry Setup (GitLab) | ||
|
|
||
| For the build process, I used GitLab registry. You'll need to configure three key parameters in your build-configuration file: | ||
|
|
||
| #### 1. `default_registry` | ||
|
|
||
| - Create a repository on GitLab | ||
| - Navigate to **Deploy** → **Container Registry** | ||
| - Copy the registry link (format: `registry.gitlab.com/your-username/repository-name`) | ||
| - Example: `registry.gitlab.com/unique-usman/kaapana-tutorial` | ||
|
|
||
| #### 2. `registry_password` | ||
|
|
||
| - Go to your repository **Settings** → **Access Tokens** | ||
| - Create a new token with read and write registry permissions | ||
| - _Note: I granted all permissions for simplicity_ | ||
|
|
||
| #### 3. `registry_username` | ||
|
|
||
| - Use the GitLab username under which you created the repository | ||
| - Since I created my repository under my personal GitLab account, I used my GitLab username | ||
|
|
||
| ### Build Process | ||
|
|
||
| Follow the comprehensive build guide available at: | ||
| https://kaapana.readthedocs.io/en/stable/installation_guide/build.html | ||
|
|
||
| ## Step 2: Server Installation | ||
|
|
||
| ### Installation Process | ||
|
|
||
| Refer to the server installation documentation: | ||
| https://kaapana.readthedocs.io/en/stable/installation_guide/server_installation.html | ||
|
|
||
| Since I didn't use any proxy configuration, I simply executed the server installation script as provided in the documentation. | ||
|
|
||
| ## Step 3: Platform Deployment | ||
|
|
||
| ### Deployment Process | ||
|
|
||
| Follow the deployment guide: | ||
| https://kaapana.readthedocs.io/en/stable/installation_guide/deployment.html | ||
|
|
||
| I ran the deployment script directly, and it worked perfectly without any additional configuration. | ||
|
|
||
| ## Post-Deployment Configuration | ||
|
|
||
| ### Accessing Your Kaapana Instance | ||
|
|
||
| After successful deployment, you'll receive a web link to access your Kaapana instance. | ||
|
|
||
| ### DNS Configuration | ||
|
|
||
| To simplify access, I mapped the deployment link to the server's IP address in `/etc/hosts`: | ||
|
|
||
| ```bash | ||
| # Example entry in /etc/hosts | ||
| your-server-ip your-kaapana-domain | ||
| ``` | ||
|
|
||
| ## Architecture Notes | ||
|
|
||
| - **Single Machine Setup**: I used only one GCP machine for the entire Kaapana deployment | ||
| - **All-in-One Configuration**: The build, installation, and deployment processes were all executed on the same machine | ||
|
|
||
| ## Key Resources | ||
|
|
||
| - [Build Guide](https://kaapana.readthedocs.io/en/stable/installation_guide/build.html) | ||
| - [Server Installation](https://kaapana.readthedocs.io/en/stable/installation_guide/server_installation.html) | ||
| - [Deployment Guide](https://kaapana.readthedocs.io/en/stable/installation_guide/deployment.html) | ||
|
|
||
| --- | ||
|
|
||
| _This guide reflects a successful single-machine Kaapana deployment using GCP and GitLab registry._ |
Uh oh!
There was an error while loading. Please reload this page.