From 0fbdb58ba4b3624696f8f23c0378a75d79a90d83 Mon Sep 17 00:00:00 2001 From: David Northover Date: Fri, 7 Nov 2025 15:10:56 -0500 Subject: [PATCH 1/4] Dev/Production Server Consistency. - await missing in prod server - excess try/catch in dev server. --- src/main/servers/main-server.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/servers/main-server.ts b/src/main/servers/main-server.ts index 08cef5d..16453ea 100644 --- a/src/main/servers/main-server.ts +++ b/src/main/servers/main-server.ts @@ -8,18 +8,14 @@ const PRODUCTION_PATH = join(__dirname, '../../../extra-resources/server/') const PRODUCTION_CONFIG = join(PRODUCTION_PATH, 'compose.yml') export async function startMainServerDevelopment(): Promise { - try { - await startDockerCompose({ - cwd: DEVELOPMENT_PATH, - config: DEVELOPMENT_CONFIG, - build: true, - onError: (err) => { - console.error('An error occurred while starting the main server:', err) - } - }) - } catch (error) { - console.error('An error occurred while starting the main server:', error) - } + await startDockerCompose({ + cwd: DEVELOPMENT_PATH, + config: DEVELOPMENT_CONFIG, + build: true, + onError: (err) => { + console.error('An error occurred while starting the main server:', err) + } + }) } export async function stopMainServerDevelopment(): Promise { @@ -30,7 +26,7 @@ export async function stopMainServerDevelopment(): Promise { } export async function startMainServerProduction(): Promise { - startDockerCompose({ + await startDockerCompose({ cwd: PRODUCTION_PATH, config: PRODUCTION_CONFIG, build: true, From 00651b0859e8b9d3dfdae29244b8e45372a88cb1 Mon Sep 17 00:00:00 2001 From: David Northover Date: Fri, 7 Nov 2025 15:44:35 -0500 Subject: [PATCH 2/4] Update cloudvolumeinterface to remap URLs when in docker. --- python/compose.yml | 5 +++-- python/ouroboros/helpers/volume_cache.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python/compose.yml b/python/compose.yml index 46307bc..d2940d7 100644 --- a/python/compose.yml +++ b/python/compose.yml @@ -9,8 +9,9 @@ services: volumes: - ouroboros-volume:/volume extra_hosts: - - "host.docker.internal:host-gateway" - + - "host.docker.internal:host-gateway" + environment: + - OUR_ENV=docker volumes: ouroboros-volume: name: ouroboros-volume \ No newline at end of file diff --git a/python/ouroboros/helpers/volume_cache.py b/python/ouroboros/helpers/volume_cache.py index f2dc651..926349c 100644 --- a/python/ouroboros/helpers/volume_cache.py +++ b/python/ouroboros/helpers/volume_cache.py @@ -1,3 +1,4 @@ +import os import sys import traceback @@ -214,6 +215,9 @@ def flush_local_cache(self): class CloudVolumeInterface: def __init__(self, source_url: str): self.source_url = source_url + if os.environ.get('OUR_ENV') == "docker": + self.source_url = self.source_url.replace("localhost", "host.docker.internal" + ).replace("127.0.0.1", "host.docker.internal") self.cv = CloudVolume(self.source_url, parallel=True, cache=True) From b89eef0b93e51f2b29337fcd4049693e998323b8 Mon Sep 17 00:00:00 2001 From: David Northover Date: Mon, 10 Nov 2025 14:29:03 -0500 Subject: [PATCH 3/4] Switch to float slice output - Fixes banding artifacts from integer conversion at edge cases. --- python/ouroboros/pipeline/slice_parallel_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ouroboros/pipeline/slice_parallel_pipeline.py b/python/ouroboros/pipeline/slice_parallel_pipeline.py index b9b4e03..b0427ec 100644 --- a/python/ouroboros/pipeline/slice_parallel_pipeline.py +++ b/python/ouroboros/pipeline/slice_parallel_pipeline.py @@ -103,7 +103,7 @@ def _process(self, input_data: tuple[any]) -> None | str: config.slice_width, config.slice_height, ) + ((num_color_channels,) if has_color_channels else ()) - temp_data = np.zeros(temp_shape, dtype=volume_cache.get_volume_dtype()) + temp_data = np.zeros(temp_shape, dtype=np.float16) imwrite( output_file_path, @@ -116,7 +116,7 @@ def _process(self, input_data: tuple[any]) -> None | str: if has_color_channels and num_color_channels > 1 else "minisblack" ), - metadata=metadata, + metadata=metadata ) except BaseException as e: return f"Error creating single tif file: {e}" From 2cab27e2c040e9d3232527fc9fa0c0813a9c1b9e Mon Sep 17 00:00:00 2001 From: David Northover Date: Mon, 10 Nov 2025 15:56:17 -0500 Subject: [PATCH 4/4] Correct handling of slices that extend beyond cloudvolume boundaries - Only grabs areas that exist - Regions beyond the bounds of the volume are set to 0. --- python/ouroboros/helpers/volume_cache.py | 21 ++++++++++++++++----- python/test/helpers/test_volume_cache.py | 12 +++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/python/ouroboros/helpers/volume_cache.py b/python/ouroboros/helpers/volume_cache.py index 926349c..f9a73d0 100644 --- a/python/ouroboros/helpers/volume_cache.py +++ b/python/ouroboros/helpers/volume_cache.py @@ -1,8 +1,9 @@ +from dataclasses import astuple import os import sys import traceback -from cloudvolume import CloudVolume, VolumeCutout +from cloudvolume import CloudVolume, VolumeCutout, Bbox import numpy as np from .bounding_boxes import BoundingBox, boxes_dim_range @@ -158,16 +159,26 @@ def remove_volume(self, volume_index: int, destroy_shared: bool = False): def download_volume( self, volume_index: int, bounding_box: BoundingBox, parallel=False ) -> VolumeCutout: - bbox = bounding_box.to_cloudvolume_bbox() + bbox = bounding_box.to_cloudvolume_bbox().astype(int) + vol_shape = NGOrder(*bbox.size3(), self.cv.cv.num_channels) + + # Limit size of area we are grabbing, in case we go out of bounds. + dl_box = Bbox.intersection(self.cv.cv.bounds, bbox) + local_min = [int(start) for start in np.subtract(dl_box.minpt, bbox.minpt)] + + local_bounds = np.s_[*[slice(start, stop) for start, stop in + zip(local_min, np.sum([local_min, dl_box.size3()], axis=0))], + :] # Download the bounding box volume if self.use_shared: - vol_shape = NGOrder(*bbox.astype(int).size3(), self.cv.cv.num_channels) volume = self.shm_host.SharedNPArray(vol_shape, np.float32) with volume as volume_data: - volume_data[:] = self.cv.cv.download(bbox, mip=self.mip, parallel=parallel) + volume_data[:] = 0 # Prob not most efficient but makes math much easier + volume_data[local_bounds] = self.cv.cv.download(dl_box, mip=self.mip, parallel=parallel) else: - volume = self.cv.cv.download(bbox, mip=self.mip, parallel=parallel) + volume = np.zeros(astuple(vol_shape)) + volume[local_bounds] = self.cv.cv.download(dl_box, mip=self.mip, parallel=parallel) # Store the volume in the cache self.volumes[volume_index] = volume diff --git a/python/test/helpers/test_volume_cache.py b/python/test/helpers/test_volume_cache.py index 2752d97..1fb2714 100644 --- a/python/test/helpers/test_volume_cache.py +++ b/python/test/helpers/test_volume_cache.py @@ -1,5 +1,9 @@ import pytest from unittest.mock import MagicMock, patch + +from cloudvolume import Bbox +import numpy as np + from ouroboros.helpers.volume_cache import ( VolumeCache, CloudVolumeInterface, @@ -19,6 +23,8 @@ def mock_cloud_volume(): mock_cv.shape = (100, 100, 100, 3) mock_cv.cache.flush = MagicMock() mock_cv.mip_volume_size = lambda mip: (100, 100, 100) + mock_cv.bounds = Bbox((0, 0, 0), (100, 100, 100)) + mock_cv.download.return_value = np.zeros((0, 10, 0, 1)) yield mock_cv @@ -173,8 +179,8 @@ def test_request_volume_for_slice(volume_cache): volume_data, bounding_box = volume_cache.request_volume_for_slice(slice_index) mock_volume_index.assert_called_once_with(slice_index) - assert bounding_box == volume_cache.bounding_boxes[1] - assert volume_data == volume_cache.volumes[1] + assert np.all(bounding_box == volume_cache.bounding_boxes[1]) + assert np.all(volume_data == volume_cache.volumes[1]) def test_create_processing_data(volume_cache): @@ -234,7 +240,7 @@ def test_volume_cache_remove_volume(volume_cache): volume_data, bounding_box = volume_cache.request_volume_for_slice(slice_index) mock_volume_index.assert_called_once_with(slice_index) - assert volume_data == volume_cache.volumes[1] + assert np.all(volume_data == volume_cache.volumes[1]) volume_cache.remove_volume(1) assert volume_cache.volumes[1] is None