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
1 change: 1 addition & 0 deletions .github/workflows/pr-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
- closed
branches:
- master
- main

env:
GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ out
.DS_Store
*.log*
documentation/site
extra-resources
extra-resources
data
ignoreme.py
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,10 @@
"editor.formatOnSave": true,
"editor.indentSize": "tabSize",
"editor.tabSize": 4
}
},
"python.testing.pytestArgs": [
"python"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
13 changes: 6 additions & 7 deletions python/ouroboros/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from multiprocessing import freeze_support
import argparse
from multiprocessing import freeze_support
import sys

from ouroboros.common.pipelines import backproject_pipeline, slice_pipeline
Expand Down Expand Up @@ -90,9 +90,8 @@ def handle_slice(args):

if args.verbose:
print("\nCalculation Statistics:\n")

for stat in pipeline.get_step_statistics():
print(pretty_json_output(stat), "\n")
stat_dict = {stat.pop("pipeline"): stat for stat in pipeline.get_step_statistics()}
print(pretty_json_output(stat_dict))


def handle_backproject(args):
Expand Down Expand Up @@ -123,9 +122,9 @@ def handle_backproject(args):

if args.verbose:
print("\nCalculation Statistics:\n")

for stat in pipeline.get_step_statistics():
print(pretty_json_output(stat), "\n")
stat_dict = {stat.pop("pipeline"): stat for stat in pipeline.get_step_statistics()}
# print(stat_dict)
print(pretty_json_output(stat_dict))


def handle_sample_options():
Expand Down
29 changes: 10 additions & 19 deletions python/ouroboros/helpers/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,38 +81,29 @@ def calculate_slice_rects(
return np.array(rects)


def generate_coordinate_grid_for_rect(rect: np.ndarray, width, height) -> np.ndarray:
def coordinate_grid(rect: np.ndarray, shape: tuple[int, int],
floor: np.ndarray = None) -> np.ndarray:
"""
Generate a coordinate grid for a rectangle.
Generate a coordinate grid for a rectangle, relative to space of rectangle.

Parameters:
----------
rect (numpy.ndarray): The corners of the rectangle as a list of 3D coordinates.
width (int): The width of the grid.
height (int): The height of the grid.
floor (ndarray): Extra minimum value to use as baseline, instead of rect[0]

Returns:
-------
numpy.ndarray: The grid of coordinates (width, height, 3).
numpy.ndarray: The grid of coordinates (height, width, 3).
"""
# Addition adds an extra rect[0] so we extend floor by it.
floor = rect[0] if floor is None else rect[0] + floor

top_left, top_right, bottom_right, bottom_left = rect
u = np.linspace(rect[0], rect[1], shape[1])
v = np.linspace(rect[0], rect[3], shape[0])

# Generate a grid of (u, v) coordinates
u = np.linspace(0, 1, width)
v = np.linspace(0, 1, height)
u, v = np.meshgrid(u, v, indexing=INDEXING) # TODO: need 'ij'?

# Interpolate the 3D coordinates
# TODO: There must be a way to do this faster
points = (
(1 - u)[:, :, np.newaxis] * (1 - v)[:, :, np.newaxis] * top_left
+ u[:, :, np.newaxis] * (1 - v)[:, :, np.newaxis] * top_right
+ (1 - u)[:, :, np.newaxis] * v[:, :, np.newaxis] * bottom_left
+ u[:, :, np.newaxis] * v[:, :, np.newaxis] * bottom_right
)

return points
return np.add(u.reshape(1, shape[1], 3), v.reshape(shape[0], 1, 3)) - floor


def slice_volume_from_grids(
Expand Down
27 changes: 13 additions & 14 deletions python/ouroboros/pipeline/backproject_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from ouroboros.helpers.slice import (
detect_color_channels,
detect_color_channels_shape,
generate_coordinate_grid_for_rect,
coordinate_grid,
make_volume_binary,
write_slices_to_volume,
write_slices_to_volume
)
from ouroboros.helpers.volume_cache import VolumeCache, get_mip_volume_sizes
from ouroboros.helpers.bounding_boxes import BoundingBox
Expand All @@ -30,6 +30,7 @@
import tifffile
import os
import multiprocessing
from pathlib import Path
import time
import numpy as np
import shutil
Expand Down Expand Up @@ -77,13 +78,15 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]:
f"The straightened volume does not exist at {straightened_volume_path}."
)

# Make sure the straightened volume is an uncompressed tif file.
# If not, convert it to an uncompressed tif file.
try:
mmap = make_tiff_memmap(straightened_volume_path, mode="r")
del mmap
except BaseException as e:
print(f"Direct memory mapping failed (Error {e})\n. Using TiffWriter.")
if Path(straightened_volume_path).is_dir():
with tifffile.TiffFile(next(Path(straightened_volume_path).iterdir())) as tif:
is_compressed = bool(tif.pages[0].compression)
else:
with tifffile.TiffFile(straightened_volume_path) as tif:
is_compressed = bool(tif.pages[0].compression)

if is_compressed:
print("Input data compressed; Rewriting.")

# Create a new path for the straightened volume
new_straightened_volume_path = join_path(
Expand Down Expand Up @@ -427,10 +430,6 @@ def process_bounding_box(
start = time.perf_counter()
straightened_volume = make_tiff_memmap(straightened_volume_path, mode="r")
_, num_channels = detect_color_channels(straightened_volume, none_value=None)
slice_width, slice_height = (
straightened_volume.shape[1],
straightened_volume.shape[2],
)
durations["memmap"].append(time.perf_counter() - start)

# Get the slices from the straightened volume
Expand All @@ -445,7 +444,7 @@ def process_bounding_box(
start = time.perf_counter()
grids = np.array(
[
generate_coordinate_grid_for_rect(slice_rects[i], slice_width, slice_height)
coordinate_grid(slice_rects[i], slices[i].shape)
for i in slice_indices
]
)
Expand Down
14 changes: 10 additions & 4 deletions python/ouroboros/pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ def get_time_statistics(self) -> dict:
# Replace custom timings with statistics about the custom timings
custom_times = self.timing["custom_times"]

loops = 0
for _, value in self.timing["custom_times"].items():
loops = max(loops, len(value))

# Remove any empty custom times
custom_times = {
key: value for key, value in custom_times.items() if len(value) > 0
Expand All @@ -121,26 +125,28 @@ def get_time_statistics(self) -> dict:
key: {
"mean": np.mean(value),
"std": np.std(value),
"min": np.min(value),
"max": np.max(value),
"min": np.min(value).astype(float),
"max": np.max(value).astype(float),
"total": np.sum(value).astype(float)
}
for key, value in custom_times.items()
}
self.timing["custom_times"] = custom_times_statistics
self.timing["loops"] = loops

return self.timing

def get_duration(self) -> float:
return self.timing["duration_seconds"]

def add_timing(self, key: str, value: float):
if key in self.timing:
if key in self.timing["custom_times"]:
self.timing["custom_times"][key].append(value)
else:
self.timing["custom_times"][key] = [value]

def add_timing_list(self, key: str, values: list[float]):
if key in self.timing:
if key in self.timing["custom_times"]:
self.timing["custom_times"][key].extend(values)
else:
self.timing["custom_times"][key] = values
Expand Down
29 changes: 19 additions & 10 deletions python/test/helpers/test_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from ouroboros.helpers.slice import (
calculate_slice_rects,
detect_color_channels,
generate_coordinate_grid_for_rect,
make_volume_binary,
slice_volume_from_grids,
write_slices_to_volume,
coordinate_grid,
write_slices_to_volume
)
from ouroboros.helpers.spline import Spline
from test.sample_data import generate_sample_curve_helix
Expand Down Expand Up @@ -57,24 +57,33 @@ def test_calculate_slice_rects():


def test_generate_coordinate_grid_for_rect():
rect = np.array([[0, 0, 1], [1, 0, 1], [1, 0, 0], [0, 0, 0]])
rect = np.array([[-42.64727347, -54.72166585, -4.78695662],
[-47.22139466, 43.8212048, -21.16926598],
[40.81561612, 55.54768491, 24.78695662],
[45.38973732, -42.99518573, 41.16926598]])
# rect = np.array([[0, 0, 0], [2, 2, 0], [2, 3, 2], [0, 1, 2]])
# rect = np.array([[ 57.35272653, 45.27833415, 95.21304338],
# [ 52.77860534, 143.8212048 , 78.83073402],
# [140.81561612, 155.54768491, 124.78695662],
# [145.38973732, 57.00481427, 141.16926598]])

WIDTH = 100
HEIGHT = 100
HEIGHT = 60

# Generate a coordinate grid for the rectangle
coordinate_grid = generate_coordinate_grid_for_rect(rect, WIDTH, HEIGHT)
cg = coordinate_grid(rect, (HEIGHT, WIDTH))

# Assert that the method returns a numpy array
assert isinstance(
coordinate_grid, np.ndarray
cg, np.ndarray
), "Coordinate grid should be a numpy array"
assert coordinate_grid.shape == (
WIDTH,
assert cg.shape == (
HEIGHT,
WIDTH,
3,
), "Coordinate grid should have shape (100, 100, 3)"
), "Coordinate grid should have shape (60, 100, 3)"
assert np.allclose(
coordinate_grid[0][0], rect[0]
cg[0][0], rect[0]
), "The first coordinate should be the top left corner of the rectangle"


Expand Down
Loading