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
21 changes: 19 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "open-dive"
version = "0.1.0"
description = "OpenDIVE (Open Diffusion Imaging Visualization for Everyone) is a command line interface tool for generating accessible, interpretable visualizations from diffusion MRI."
readme = "README.md"
requires-python = ">=3.13"
requires-python = ">=3.10"
dependencies = [
"dipy>=1.10.0",
"fury>=0.11.0",
]
classifiers = ["Private :: Do Not Upload"]
maintainers = [
{name = "Adam Saunders", email = "adam.m.saunders@vanderbilt.edu"},
{name = "Elyssa McMaster", email = "elyssa.m.mcmaster@vanderbilt.edu"}
]
keywords = ["diffusion MRI", "visualization", "medical imaging"]
classifiers = ["Private :: Do Not Upload"]

[project.urls]
Repository = "https://github.com/MASILab/open_dive"
Issues = "https://github.com/MASILab/open_dive/issues"


[project.scripts]
nifti2png = "open_dive.scripts.nifti2png:main"
Empty file removed src/__init__.py/viz.py
Empty file.
File renamed without changes.
59 changes: 59 additions & 0 deletions src/open_dive/scripts/nifti2png.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import argparse
from pathlib import Path

from ..viz import plot_nifti


def main():
# Create args
parser = argparse.ArgumentParser(description="Plot a slice of a NIFTI file")
parser.add_argument("nifti_path", type=Path, help="Path to NIFTI to plot")
parser.add_argument(
"-s", "--slice", default="m", help='Slice to plot or "m" for middle slice'
)
parser.add_argument(
"-o",
"--orientation",
default="axial",
help='Can be "axial", "sagittal" or "coronal"',
)
parser.add_argument(
"--size", default=(600, 400), help="Size of window, by default (600,400)"
)
parser.add_argument("--save_path", help="Optional path to save to")
parser.add_argument(
"--interactive",
action="store_true",
help="Whether to interactively show the scene",
)
parser.add_argument(
"--value_range",
type=int,
nargs=2,
help="Optional value range to pass to slicer. Default is min/max of image.",
)
parser.add_argument(
"--interpolation",
default="nearest",
help="Interpolation method to use (nearest or linear). Default is 'nearest'.",
)
parser.add_argument(
"--not_radiological",
action="store_true",
help="Do not plot in radiological view (i.e., subject right is on image right)",
)

args = parser.parse_args()

# Plot the NIFTI
plot_nifti(
args.nifti_path,
data_slice=args.slice,
orientation=args.orientation,
size=args.size,
radiological=not args.not_radiological,
save_path=args.save_path,
interactive=args.interactive,
value_range=args.value_range,
interpolation=args.interpolation,
)
90 changes: 90 additions & 0 deletions src/open_dive/viz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import nibabel as nib
import numpy as np
from dipy.viz import window
from fury.actor import slicer


def plot_nifti(
nifti_path,
data_slice="m",
orientation="axial",
size=(600, 400),
radiological=True,
save_path=None,
interactive=True,
**kwargs,
):
"""Create a 2D rendering of a NIFTI slice.

Parameters
----------
nifti_path : str or Path
Path to NIFTI to plot
data_slice : str or int, optional
Slice to plot or "m" for middle slice, by default "m"
orientation : str, optional
Can be "axial", "sagittal" or "coronal", by default "axial"
size : tuple, optional
Size of window, by default (600,400)
radiological : bool, optional
Whether to plot in radiological view (subject right is on image left), by default True
save_path : str or Path, optional
Optional path to save to, by default None
interactive : bool, optional
Whether to interactively show the scene, by default True
**kwargs
Additional keyword arguments to pass to fury.actor.slicer
"""

# Load the data and convert to RAS
nifti = nib.load(nifti_path)
nifti = nib.as_closest_canonical(nifti)

# Get the data and affine
data = nifti.get_fdata()
affine = nifti.affine

if radiological and orientation == "axial":
data = np.flip(data, axis=0)

# Set up slicer and window
slice_actor = slicer(data, affine=affine, **kwargs)
scene = window.Scene()
scene.add(slice_actor)

# Get slice if not defined
if orientation == "axial":
data_slice = data.shape[2] // 2 if data_slice == "m" else data_slice
slice_actor.display_extent(
0, data.shape[0], 0, data.shape[1], data_slice, data_slice
)

camera_pos = (0, 0, 1)
camera_up = (0, 1, 0)
elif orientation == "coronal":
data_slice = data.shape[1] // 2 if data_slice == "m" else data_slice
slice_actor.display_extent(
0, data.shape[0], data_slice, data_slice, 0, data.shape[2]
)

camera_pos = (0, 1, 0)
camera_up = (0, 0, 1)
elif orientation == "sagittal":
data_slice = data.shape[0] // 2 if data_slice == "m" else data_slice
slice_actor.display_extent(
data_slice, data_slice, 0, data.shape[1], 0, data.shape[2]
)

camera_pos = (1, 0, 0)
camera_up = (0, 0, 1)
camera_focal = (0, 0, 0)

# Set up camera
scene.set_camera(position=camera_pos, focal_point=camera_focal, view_up=camera_up)

# Show the scene
if save_path:
window.record(scene=scene, out_path=save_path, size=size, reset_camera=True)

if interactive:
window.show(scene, size=size, reset_camera=True)
Loading