Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions WrapImage/nifti_wrapper_kaapana.py
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
Comment thread
Unique-Usman marked this conversation as resolved.

# 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"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just saving the output files to this folder directly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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}")

93 changes: 93 additions & 0 deletions kaapana_ivim_osipi/INSTALL_KAAPANA.md
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._
Loading
Loading