Skip to content

Commit

Permalink
Add option to change output mip level.
Browse files Browse the repository at this point in the history
  • Loading branch information
We-Gold committed Aug 6, 2024
1 parent b103da7 commit bd86039
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 6 deletions.
2 changes: 1 addition & 1 deletion python/ouroboros/helpers/coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def convert_points_between_volumes(
Parameters
----------
points : np.ndarray
The points to convert.
The points to convert. (N, 3)
source_shape : tuple
The shape of the volume that the points are currently in.
target_shape : tuple
Expand Down
2 changes: 2 additions & 0 deletions python/ouroboros/helpers/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class SliceOptions(CommonOptions):
connect_start_and_end: bool = (
False # Whether to connect the start and end of the given annotation points
)
annotation_mip_level: int = 0 # MIP level for the annotation layer
output_mip_level: int = 0 # MIP level for the output image layer

@field_serializer("bounding_box_params")
def serialize_bounding_box_params(self, value: BoundingBoxParams):
Expand Down
22 changes: 22 additions & 0 deletions python/ouroboros/helpers/volume_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,25 @@ def get_resolution_um(self, mip: int):

def flush_cache(self):
self.cv.cache.flush()


def get_mip_volume_sizes(source_url: str) -> dict:
"""
Get the volume sizes for all available MIPs.
Parameters:
----------
source_url (str): The URL of the cloud volume.
Returns:
-------
dict: A dictionary containing the volume sizes for all available MIPs.
"""

try:
cv = CloudVolumeInterface(source_url)
result = {mip: cv.get_volume_shape(mip) for mip in cv.available_mips}
except BaseException:
return {}

return result
30 changes: 28 additions & 2 deletions python/ouroboros/pipeline/slices_geom_pipeline.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from ouroboros.helpers.coordinates import convert_points_between_volumes
from ouroboros.helpers.slice import calculate_slice_rects
from ouroboros.helpers.spline import Spline
from ouroboros.helpers.volume_cache import get_mip_volume_sizes
from .pipeline import PipelineStep
from ouroboros.helpers.options import SliceOptions
import numpy as np


class SlicesGeometryPipelineStep(PipelineStep):
def __init__(self) -> None:
super().__init__(inputs=("slice_options", "sample_points"))
super().__init__(inputs=("slice_options", "sample_points", "source_url"))

def _process(self, input_data: tuple[any]) -> None | str:
config, sample_points, pipeline_input = input_data
config, sample_points, source_url, pipeline_input = input_data

# Verify that a config object is provided
if not isinstance(config, SliceOptions):
Expand All @@ -20,6 +22,30 @@ def _process(self, input_data: tuple[any]) -> None | str:
if not isinstance(sample_points, np.ndarray):
return "Input data must contain an array of sample points."

# Rescale the sample points if the option is enabled
if config.annotation_mip_level != config.output_mip_level:
mip_sizes = get_mip_volume_sizes(source_url)

if len(mip_sizes) == 0:
return "Failed to get the mip sizes from the volume."

if (
config.annotation_mip_level not in mip_sizes
or config.output_mip_level not in mip_sizes
):
return "The specified mip levels are not present in the volume."

sample_points = convert_points_between_volumes(
sample_points,
mip_sizes[config.annotation_mip_level],
mip_sizes[config.output_mip_level],
)

# Update the pipeline input with the rescaled sample points
# Note: this ensures that the rescaled sample points are saved to the JSON file,
# which is important for the backprojection step
pipeline_input.sample_points = sample_points

spline = Spline(sample_points, degree=3)

# Plot equidistant points along the spline
Expand Down
1 change: 1 addition & 0 deletions python/ouroboros/pipeline/volume_cache_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def _process(self, input_data: tuple[any]) -> None | str:
link_rects,
cloud_volume_interface,
flush_cache=config.flush_cache,
mip=config.output_mip_level,
)

# Update the pipeline input with the volume cache
Expand Down
26 changes: 23 additions & 3 deletions python/test/helpers/test_volume_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ouroboros.helpers.volume_cache import (
VolumeCache,
CloudVolumeInterface,
get_mip_volume_sizes,
)
from ouroboros.helpers.bounding_boxes import BoundingBox

Expand All @@ -15,7 +16,7 @@ def mock_cloud_volume():
mock_cv.dtype = "uint8"
mock_cv.shape = (100, 100, 100, 3)
mock_cv.cache.flush = MagicMock()
mock_cv.mip_volume_size = lambda mip: (100, 100, 100, 3)
mock_cv.mip_volume_size = lambda mip: (100, 100, 100)
yield mock_cv


Expand Down Expand Up @@ -115,7 +116,7 @@ def test_cloud_volume_interface_to_dict(cloud_volume_interface):

def test_cloud_volume_interface_get_volume_shape(cloud_volume_interface):
shape = cloud_volume_interface.get_volume_shape(0)
assert shape == (100, 100, 100, 3)
assert shape == (100, 100, 100)


def test_cloud_volume_interface_get_resolution_nm(cloud_volume_interface):
Expand Down Expand Up @@ -154,7 +155,7 @@ def test_volume_cache_get_available_mips(volume_cache):


def test_volume_cache_get_shape(volume_cache):
assert volume_cache.get_volume_shape() == (100, 100, 100, 3)
assert volume_cache.get_volume_shape() == (100, 100, 100)


def test_request_volume_for_slice(volume_cache):
Expand Down Expand Up @@ -186,3 +187,22 @@ def mock_download_func(volume_index, bounding_box, parallel):
assert processing_data[1] == volume_cache.bounding_boxes[0]
assert processing_data[2] == [0]
assert processing_data[3] == 0


def test_get_mip_volume_sizes(mock_cloud_volume):
with patch.object(mock_cloud_volume, "mip_volume_size") as mock_mip_volume_size:
mock_mip_volume_size.return_value = (100, 100, 100)

sizes = get_mip_volume_sizes("test_source_url")

assert sizes == {0: (100, 100, 100), 1: (100, 100, 100), 2: (100, 100, 100)}


def test_get_mip_volume_sizes_error(mock_cloud_volume):
with patch.object(mock_cloud_volume, "mip_volume_size") as mock_mip_volume_size:
mock_mip_volume_size.return_value = (100, 100, 100)
mock_mip_volume_size.side_effect = Exception

sizes = get_mip_volume_sizes("test_source_url")

assert sizes == {}
2 changes: 2 additions & 0 deletions src/renderer/src/interfaces/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ export class SliceOptionsFile extends CompoundEntry {
new Entry('slice_height', 'Slice Height', 120, 'number'),
new Entry('output_file_folder', 'Output File Folder', './', 'filePath'),
new Entry('output_file_name', 'Output File Name', 'sample', 'string'),
new Entry('annotation_mip_level', 'Annotation MIP Level', 0, 'number'),
new Entry('output_mip_level', 'Output MIP Level', 0, 'number'),
new Entry('dist_between_slices', 'Distance Between Slices', 1, 'number'),
new Entry('make_single_file', 'Output Single File', true, 'boolean'),
new Entry('connect_start_and_end', 'Connect Endpoints', false, 'boolean'),
Expand Down

0 comments on commit bd86039

Please sign in to comment.