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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"version": "1.2.8rc1",
"description": "Quickly extract ROIs from cloud-hosted medical scans.",
"main": "./out/main/index.js",
"author": "Weaver Goldman <[email protected]>",
"homepage": "https://github.com/We-Gold/ouroboros",
"author": "Weaver Goldman <[email protected]> and David Northover <[email protected]>",
"homepage": "https://github.com/ChengLabResearch/ouroboros",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
Expand Down
62 changes: 61 additions & 1 deletion python/ouroboros/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import argparse
import json
from multiprocessing import freeze_support
from pathlib import Path
import sys

from ouroboros.common.pipelines import backproject_pipeline, slice_pipeline
import numpy as np
from scipy.spatial.transform import Rotation as R

from ouroboros.common.pipelines import backproject_pipeline, slice_pipeline, geometry_pipeline
from ouroboros.helpers.models import pretty_json_output
from ouroboros.helpers.options import (
DEFAULT_BACKPROJECT_OPTIONS,
Expand Down Expand Up @@ -57,6 +62,21 @@ def main():
help="Export sample options files into the current folder.",
)

# Create the parser for the geometry command
parser_geometry = subparsers.add_parser(
"geometry", help="Output the geometry of the spline."
)
parser_geometry.add_argument(
"options",
type=str,
help="The path to the options json file.",
)
parser_geometry.add_argument(
"--include-ng",
action="store_true",
help="Include Neuroglancer points and angles."
)

# Parse the arguments
args = parser.parse_args()

Expand All @@ -68,10 +88,50 @@ def main():
handle_backproject(args)
case "sample-options":
handle_sample_options()
case "geometry":
handle_geometry(args)
case _:
parser.print_help()


def handle_geometry(args):
print(f"Loading slice options from: {args.options}")
slice_options = SliceOptions.load_from_json(args.options)

if isinstance(slice_options, str):
print("Exiting due to errors loading slice options.", file=sys.stderr)
sys.exit(1)

print("Slice options loaded successfully.")
pipeline, input_data = geometry_pipeline(slice_options)

_, error = pipeline.process(input_data)

if error:
print(f"Pipeline Error: {error}", file=sys.stderr)

rects_output = Path(slice_options.output_file_folder, f"{slice_options.output_file_name}_rects").with_suffix(".npy")
np.save(rects_output, input_data.slice_rects)

if args.include_ng:
ng_output = Path(slice_options.output_file_folder, f"{slice_options.output_file_name}_ng").with_suffix(".json")
rot_matrix = np.empty((3, len(input_data.slice_rects), 3))

rot_matrix[0] = (input_data.slice_rects[:, 1, :] - input_data.slice_rects[:, 0, :]) / slice_options.slice_width
rot_matrix[1] = (input_data.slice_rects[:, 3, :] - input_data.slice_rects[:, 0, :]) / slice_options.slice_height
rot_matrix[2] = np.divide(np.cross(rot_matrix[0], rot_matrix[1], axis=1),
(slice_options.slice_width * slice_options.slice_height))
centers = (input_data.slice_rects[:, 0, :] + input_data.slice_rects[:, 2, :]) / 2
quats = R.from_matrix(rot_matrix.transpose(1, 0, 2)).as_quat()

ng_points = []
for point in range(len(input_data.slice_rects)):
ng_points.append({"position": centers[point].tolist(), "crossSectionOrientation": quats[point].tolist()})

with open(ng_output, "w") as handle:
json.dump(ng_points, handle)


def handle_slice(args):
print(f"Loading slice options from: {args.options}")
slice_options = SliceOptions.load_from_json(args.options)
Expand Down
31 changes: 31 additions & 0 deletions python/ouroboros/common/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,37 @@
)


def geometry_pipeline(slice_options: SliceOptions) -> tuple[Pipeline, PipelineInput]:
"""
Creates a pipeline for slicing a volume, as well as the default input data for the pipeline.

Parameters
----------
slice_options : SliceOptions
The options for slicing the volume.
include-ng : bool, optional
Whether to include neuroglancer output data, default false.

Returns
-------
tuple[Pipeline, PipelineInput]
The pipeline for slicing the volume and the default input data for the pipeline
"""

pipeline = Pipeline(
[
ParseJSONPipelineStep(),
SlicesGeometryPipelineStep()
]
)

default_input_data = PipelineInput(
slice_options=slice_options, json_path=slice_options.neuroglancer_json
)

return pipeline, default_input_data


def slice_pipeline(slice_options: SliceOptions, verbose: bool = False) -> tuple[Pipeline, PipelineInput]:
"""
Creates a pipeline for slicing a volume, as well as the default input data for the pipeline.
Expand Down
1 change: 1 addition & 0 deletions python/ouroboros/helpers/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def volume_from_intermediates(path: Path, shape: DataShape, discrete: bool = Fal
nz = np.flatnonzero(vol[0])
vol[0, nz] /= vol[1, nz]
if discrete:
vol[0] = np.round(vol[0], 2)
vol[0, vol[0] % 1 != 0] = 0
return vol[0]

Expand Down
Loading