From a4b47213839d6eaa332a7ea87ef918a974a397c6 Mon Sep 17 00:00:00 2001 From: Saurabh Mogre Date: Wed, 26 Jun 2024 10:58:47 -0700 Subject: [PATCH 01/23] Add gradient weights to Environment.py and Gradient.py --- cellpack/autopack/Gradient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 4d6478ccf..7faa80cc0 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -50,6 +50,7 @@ from random import random import bisect from cellpack.autopack.utils import get_distances_from_point +import matplotlib.pyplot as plt class Gradient: From 495b6acafc43503eb29b7b33cee23ee446e41d8c Mon Sep 17 00:00:00 2001 From: Saurabh Mogre Date: Wed, 26 Jun 2024 14:35:40 -0700 Subject: [PATCH 02/23] Remove unused import --- cellpack/autopack/Gradient.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 7faa80cc0..4d6478ccf 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -50,7 +50,6 @@ from random import random import bisect from cellpack.autopack.utils import get_distances_from_point -import matplotlib.pyplot as plt class Gradient: From e50d383cd1afea4a6a3e65026a570a33e90baa38 Mon Sep 17 00:00:00 2001 From: Saurabh Mogre Date: Thu, 12 Dec 2024 11:32:27 -0800 Subject: [PATCH 03/23] Refactor Environment.py and utils.py This commit refactors the `Environment.py` file by separating the loading of grid, compartment grids, and mesh store into separate loops. It also clears the triangles_tree cache and resets the grid. Additionally, the `load_object_from_pickle` function has been removed from `utils.py` as it is no longer used. --- cellpack/autopack/Environment.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cellpack/autopack/Environment.py b/cellpack/autopack/Environment.py index 101dce8f5..fa7937cb9 100644 --- a/cellpack/autopack/Environment.py +++ b/cellpack/autopack/Environment.py @@ -853,6 +853,10 @@ def restore_grids_from_pickle(self, grid_file_path): comp_objs = [] mesh_store_objs = [] + grid_objs = [] + comp_objs = [] + mesh_store_objs = [] + with open(grid_file_path, "rb") as file_obj: while True: try: From dab1819cd90fe86b8ad9d7ce9756277d74a4ef0e Mon Sep 17 00:00:00 2001 From: mogres Date: Wed, 27 Aug 2025 13:32:36 -0700 Subject: [PATCH 04/23] Ignore venv folders --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cd7fd47a4..1b342013d 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ dask-worker-space .env # virtualenv +**venv** .venv venv/ ENV/ From 208e9fbc3fecb7036d1d4482a29800073ecc8ab7 Mon Sep 17 00:00:00 2001 From: mogres Date: Wed, 24 Sep 2025 17:16:01 -0700 Subject: [PATCH 05/23] Update logging level --- cellpack/logging.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cellpack/logging.conf b/cellpack/logging.conf index 3741e4e4a..49689f7bf 100644 --- a/cellpack/logging.conf +++ b/cellpack/logging.conf @@ -12,32 +12,32 @@ level=INFO handlers=consoleHandler [logger_autopack] -level=CRITICAL +level=INFO qualname=autopack handlers=consoleHandler [logger_compartment] -level=CRITICAL +level=INFO qualname=compartment handlers=consoleHandler [logger_env] -level=CRITICAL +level=INFO qualname=env handlers=consoleHandler [logger_grid] -level=CRITICAL +level=INFO qualname=grid handlers=consoleHandler [logger_ingredient] -level=CRITICAL +level=INFO qualname=ingredient handlers=consoleHandler [logger_recipe] -level=CRITICAL +level=INFO qualname=recipe handlers=consoleHandler From cc22b02114dd957d2bd47f9c621091ba0692a279 Mon Sep 17 00:00:00 2001 From: mogres Date: Fri, 26 Sep 2025 17:09:47 -0700 Subject: [PATCH 06/23] Update `aicsimageio` to `bioio` - Also update the logging of recipe validation to `debug` --- cellpack/bin/pack.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cellpack/bin/pack.py b/cellpack/bin/pack.py index 4b4a23bba..defa3615d 100644 --- a/cellpack/bin/pack.py +++ b/cellpack/bin/pack.py @@ -1,3 +1,4 @@ +import json import logging import logging.config import os @@ -5,6 +6,7 @@ from pathlib import Path import fire +from pydantic import ValidationError from cellpack import autopack from cellpack.autopack import upy From f4378a9846e467d244e7784aede8e2608de1a951 Mon Sep 17 00:00:00 2001 From: mogres Date: Mon, 29 Sep 2025 19:19:47 -0700 Subject: [PATCH 07/23] Suppress logging outputs for lower level functions --- cellpack/logging.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cellpack/logging.conf b/cellpack/logging.conf index 49689f7bf..3741e4e4a 100644 --- a/cellpack/logging.conf +++ b/cellpack/logging.conf @@ -12,32 +12,32 @@ level=INFO handlers=consoleHandler [logger_autopack] -level=INFO +level=CRITICAL qualname=autopack handlers=consoleHandler [logger_compartment] -level=INFO +level=CRITICAL qualname=compartment handlers=consoleHandler [logger_env] -level=INFO +level=CRITICAL qualname=env handlers=consoleHandler [logger_grid] -level=INFO +level=CRITICAL qualname=grid handlers=consoleHandler [logger_ingredient] -level=INFO +level=CRITICAL qualname=ingredient handlers=consoleHandler [logger_recipe] -level=INFO +level=CRITICAL qualname=recipe handlers=consoleHandler From 7c3cf593aefeb41536637a4db47db5fd8444d2a2 Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 30 Sep 2025 14:54:14 -0700 Subject: [PATCH 08/23] Update logs in `ImageWriter.py` --- cellpack/autopack/writers/ImageWriter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cellpack/autopack/writers/ImageWriter.py b/cellpack/autopack/writers/ImageWriter.py index c429cb537..f0f8fd270 100644 --- a/cellpack/autopack/writers/ImageWriter.py +++ b/cellpack/autopack/writers/ImageWriter.py @@ -1,9 +1,12 @@ +import logging from pathlib import Path import numpy from bioio_ome_tiff.writers import OmeTiffWriter from scipy.ndimage import convolve +log = logging.getLogger(__name__) + """ ImageWriter provides a class to export cellpack packings as tiff images """ @@ -209,7 +212,7 @@ def export_image(self): """ Saves the results as a tiff file """ - print(f"Exporting image to {self.output_path}") + log.debug(f"Exporting image to {self.output_path}") ( concatenated_image, channel_names, From f2a96d7a9a996bb32476c1e72ab10837fe799e04 Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 2 Oct 2025 09:55:02 -0700 Subject: [PATCH 09/23] Update path preference loading to catch empty json --- cellpack/autopack/__init__.py | 68 ++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/cellpack/autopack/__init__.py b/cellpack/autopack/__init__.py index 0ef25ef44..77ddaf465 100755 --- a/cellpack/autopack/__init__.py +++ b/cellpack/autopack/__init__.py @@ -150,27 +150,53 @@ def checkPath(): log.error(str(autopack_path_pref_file) + "file is not found") checkPath() -doit = False -if os.path.isfile(autopack_user_path_pref_file): - f = open(autopack_user_path_pref_file, "r") - doit = True -elif os.path.isfile(autopack_path_pref_file): - f = open(autopack_path_pref_file, "r") - doit = True -if doit: - log.info(f"autopack_path_pref_file {autopack_path_pref_file}") - pref_path = json.load(f) - f.close() - if "autoPACKserver" not in pref_path: - log.warning(f"problem with autopack_path_pref_file {autopack_path_pref_file}") - else: - autoPACKserver = pref_path["autoPACKserver"] - if "filespath" in pref_path: - if pref_path["filespath"] != "default": - filespath = pref_path["filespath"] - if "autopackdir" in pref_path: - if pref_path["autopackdir"] != "default": - autopackdir = pref_path["autopackdir"] + +def load_path_preferences(): + """Load path preferences from user or default preference files.""" + global autoPACKserver, filespath, autopackdir + + # Determine which preference file to use + pref_file = None + if os.path.isfile(autopack_user_path_pref_file): + pref_file = autopack_user_path_pref_file + elif os.path.isfile(autopack_path_pref_file): + pref_file = autopack_path_pref_file + + if pref_file is None: + log.warning("No preference files found") + return + + try: + with open(pref_file, "r") as f: + content = f.read().strip() + if not content: + log.warning(f"Preference file {pref_file} is empty") + return + + pref_path = json.loads(content) + + if not isinstance(pref_path, dict): + log.warning(f"Invalid preference file format in {pref_file}") + return + + if "autoPACKserver" not in pref_path: + log.warning(f"Missing 'autoPACKserver' key in {pref_file}") + else: + autoPACKserver = pref_path["autoPACKserver"] + + if "filespath" in pref_path and pref_path["filespath"] != "default": + filespath = pref_path["filespath"] + + if "autopackdir" in pref_path and pref_path["autopackdir"] != "default": + autopackdir = pref_path["autopackdir"] + + except json.JSONDecodeError as e: + log.error(f"Failed to parse JSON in {pref_file}: {e}") + except Exception as e: + log.error(f"Error loading preferences from {pref_file}: {e}") + + +load_path_preferences() REPLACE_PATH = { From 6774f79e6b3e7ec523ae5acd9dd1b741e0650e07 Mon Sep 17 00:00:00 2001 From: mogres Date: Fri, 3 Oct 2025 14:50:05 -0700 Subject: [PATCH 10/23] Update logging for recipe validation --- cellpack/bin/validate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cellpack/bin/validate.py b/cellpack/bin/validate.py index 7a419cb8b..f4ecdf8a1 100644 --- a/cellpack/bin/validate.py +++ b/cellpack/bin/validate.py @@ -1,3 +1,4 @@ +import json import logging import logging.config import fire From 8b33b985189add90098def8758426d9c6e1a7bb8 Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 21 Oct 2025 09:42:21 -0700 Subject: [PATCH 11/23] sort imports --- cellpack/autopack/writers/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cellpack/autopack/writers/__init__.py b/cellpack/autopack/writers/__init__.py index f98a5c590..14b86ad69 100644 --- a/cellpack/autopack/writers/__init__.py +++ b/cellpack/autopack/writers/__init__.py @@ -5,12 +5,13 @@ import json import os -import numpy from collections import OrderedDict +import numpy + +import cellpack.autopack.transformation as tr from cellpack import autopack from cellpack.autopack.ingredient.grow import ActinIngredient, GrowIngredient -import cellpack.autopack.transformation as tr def updatePositionsRadii(ingr): From 0efa3bb2990c7b6edd10f5b9a40cc6a41ba79bba Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 21 Oct 2025 09:42:29 -0700 Subject: [PATCH 12/23] Update uv lock --- uv.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/uv.lock b/uv.lock index 48582665f..48d827f46 100644 --- a/uv.lock +++ b/uv.lock @@ -244,7 +244,6 @@ dev = [ ] docs = [ { name = "linkify-it-py" }, - { name = "mdutils" }, { name = "myst-parser" }, ] lint = [ @@ -297,7 +296,6 @@ dev = [ ] docs = [ { name = "linkify-it-py" }, - { name = "mdutils" }, { name = "myst-parser" }, ] lint = [ From 026e9e8a9f515c77f86193a9f9ec24a25ab25d13 Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 21 Oct 2025 10:39:53 -0700 Subject: [PATCH 13/23] Allow gradient weights to be specified as a dict while combining --- cellpack/autopack/Gradient.py | 13 +++++++++++-- cellpack/autopack/ingredient/Ingredient.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 4d6478ccf..c0b321338 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -46,9 +46,11 @@ # TODO: fix the save/restore grid """ -import numpy -from random import random import bisect +from random import random + +import numpy + from cellpack.autopack.utils import get_distances_from_point @@ -133,6 +135,13 @@ def get_combined_gradient_weight(gradient_list, gradient_weights=None): for i in range(len(gradient_list)): weight_list[i] = Gradient.scale_between_0_and_1(gradient_list[i].weight) + if isinstance(gradient_weights, dict): + total = sum(gradient_weights.values()) + gradient_weights = [ + gradient_weights.get(gradient.name, 0) / total + for gradient in gradient_list + ] + combined_weight = numpy.average(weight_list, axis=0, weights=gradient_weights) combined_weight = Gradient.scale_between_0_and_1(combined_weight) diff --git a/cellpack/autopack/ingredient/Ingredient.py b/cellpack/autopack/ingredient/Ingredient.py index d39d03620..c8f01faf9 100644 --- a/cellpack/autopack/ingredient/Ingredient.py +++ b/cellpack/autopack/ingredient/Ingredient.py @@ -425,7 +425,7 @@ def validate_ingredient_info(ingredient_info): if isinstance(ingredient_info["gradient"], list): if "gradient_weights" in ingredient_info: # check if gradient_weights are missing - if not isinstance(ingredient_info["gradient_weights"], list): + if not isinstance(ingredient_info["gradient_weights"], list | dict): raise Exception( f"Invalid gradient weights for ingredient {ingredient_info['name']}" ) From 630c80c5523082b0375837b0a502efbcb926f466 Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 21 Oct 2025 14:46:56 -0700 Subject: [PATCH 14/23] Add uniform weight map option --- cellpack/autopack/Gradient.py | 12 ++++++++++++ .../interface_objects/archive/gradient_data.py | 1 + 2 files changed, 13 insertions(+) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index c0b321338..6cb164cd1 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -262,6 +262,8 @@ def build_weight_map(self, bb, master_grid_positions): self.build_radial_weight_map(bb, master_grid_positions) elif self.mode == "surface": self.build_surface_distance_weight_map() + elif self.mode == "uniform": + self.build_uniform_weight_map(master_grid_positions) def get_gauss_weights(self, number_of_points, degree=5): """ @@ -326,6 +328,13 @@ def build_axis_weight_map(self, bb, master_grid_positions): self.distances = numpy.abs((master_grid_positions[:, ind] - mini)) self.set_weights_by_mode() + def build_uniform_weight_map(self, master_grid_positions): + """ + Build a uniform weight map + """ + self.distances = numpy.zeros(len(master_grid_positions), dtype=numpy.uint8) + self.weight = numpy.ones(len(master_grid_positions)) + def set_weights_by_mode(self): self.scaled_distances = Gradient.scale_between_0_and_1(self.distances) @@ -354,6 +363,9 @@ def set_weights_by_mode(self): self.weight = numpy.exp( -self.scaled_distances / self.weight_mode_settings["decay_length"] ) + else: + raise ValueError(f"Unknown weight mode: {self.weight_mode}") + # normalize the weight self.weight = Gradient.scale_between_0_and_1(self.weight) diff --git a/cellpack/autopack/interface_objects/archive/gradient_data.py b/cellpack/autopack/interface_objects/archive/gradient_data.py index 4371ecb17..3bcdc5a77 100644 --- a/cellpack/autopack/interface_objects/archive/gradient_data.py +++ b/cellpack/autopack/interface_objects/archive/gradient_data.py @@ -21,6 +21,7 @@ class GradientModes(MetaEnum): VECTOR = "vector" RADIAL = "radial" SURFACE = "surface" + UNIFORM = "uniform" class WeightModes(MetaEnum): From 8e035a86bf86edc0e1b3752ac6d567792a045efc Mon Sep 17 00:00:00 2001 From: mogres Date: Tue, 21 Oct 2025 15:03:04 -0700 Subject: [PATCH 15/23] Fix gradient weight scaling for equal weights --- cellpack/autopack/Gradient.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 6cb164cd1..a3b7c71c6 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -112,6 +112,8 @@ def scale_between_0_and_1(values): """ max_value = numpy.nanmax(values) min_value = numpy.nanmin(values) + if max_value == min_value: + return values return (values - min_value) / (max_value - min_value) @staticmethod From e92ddea7f412ceb1cb05659c24da3c2e4259474a Mon Sep 17 00:00:00 2001 From: mogres Date: Wed, 22 Oct 2025 09:42:06 -0700 Subject: [PATCH 16/23] Update scaling to just divide by max value --- cellpack/autopack/Gradient.py | 5 +---- cellpack/autopack/upy/simularium/simularium_helper.py | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index a3b7c71c6..5ef5e22cd 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -111,10 +111,7 @@ def scale_between_0_and_1(values): Scale values between 0 and 1 """ max_value = numpy.nanmax(values) - min_value = numpy.nanmin(values) - if max_value == min_value: - return values - return (values - min_value) / (max_value - min_value) + return (values / max_value) if max_value != 0 else values @staticmethod def get_combined_gradient_weight(gradient_list, gradient_weights=None): diff --git a/cellpack/autopack/upy/simularium/simularium_helper.py b/cellpack/autopack/upy/simularium/simularium_helper.py index 3908752bb..8801489bc 100644 --- a/cellpack/autopack/upy/simularium/simularium_helper.py +++ b/cellpack/autopack/upy/simularium/simularium_helper.py @@ -347,8 +347,11 @@ def add_grid_data_to_scene(self, incoming_name, positions, values, radius=0.5): positions, values = self.sort_values(positions, values) - normalized_values = (values - np.min(values)) / ( - np.max(values) - np.min(values) + max_value = np.nanmax(values) + min_value = np.nanmin(values) + value_range = max_value - min_value + normalized_values = ( + (values - min_value) / value_range if value_range != 0 else values ) colormap = matplotlib.cm.Reds(normalized_values) From ea05c4484b00a14d0b2eaa0052b983516fb58c14 Mon Sep 17 00:00:00 2001 From: mogres Date: Sun, 26 Oct 2025 20:00:52 -0800 Subject: [PATCH 17/23] Add recipe to test gradients --- cellpack/tests/recipes/v2/test_gradient.json | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 cellpack/tests/recipes/v2/test_gradient.json diff --git a/cellpack/tests/recipes/v2/test_gradient.json b/cellpack/tests/recipes/v2/test_gradient.json new file mode 100644 index 000000000..5632eefda --- /dev/null +++ b/cellpack/tests/recipes/v2/test_gradient.json @@ -0,0 +1,102 @@ +{ + "version": "default", + "format_version": "2.0", + "name": "test_gradient", + "bounding_box": [ + [ + -100, + -100, + 0 + ], + [ + 100, + 100, + 1 + ] + ], + "gradients": { + "radial_gradient": { + "mode": "radial", + "description": "Radial gradient from the center", + "weight_mode": "cube", + "pick_mode": "rnd", + "mode_settings": { + "direction": [ + 0, + 0, + 0 + ], + "radius": 150, + "center": [ + 0, + 0, + 0 + ] + } + }, + "vector_gradient": { + "mode": "vector", + "description": "Gradient away from the plane formed by center and vector", + "weight_mode": "cube", + "pick_mode": "rnd", + "mode_settings": { + "direction": [ + 1, + 1, + 0 + ] + } + }, + "uniform_gradient": { + "mode": "uniform", + "description": "Uniform gradient" + } + }, + "objects": { + "base": { + "packing_mode": "gradient", + "principal_vector": [ + 0, + 0, + 1 + ], + "place_method": "jitter", + "jitter_attempts": 100, + "available_regions": { + "interior": {}, + "surface": {}, + "outer_leaflet": {}, + "inner_leaflet": {} + } + }, + "sphere": { + "type": "single_sphere", + "inherit": "base", + "color": [ + 0.5, + 0.5, + 0.5 + ], + "gradient": "radial_gradient", + "radius": 5, + "max_jitter": [ + 1, + 1, + 0 + ] + } + }, + "composition": { + "space": { + "regions": { + "interior": [ + "A" + ] + } + }, + "A": { + "object": "sphere", + "count": 100 + } + } +} \ No newline at end of file From c406e3fd8bfbbe445f8eca243a98381f391a1e9b Mon Sep 17 00:00:00 2001 From: mogres Date: Sun, 26 Oct 2025 20:02:20 -0800 Subject: [PATCH 18/23] Rename scaling method to normalize_by_max_value and update references --- cellpack/autopack/Gradient.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 5ef5e22cd..6d0e2605b 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -106,9 +106,9 @@ def update_ingredient_gradient(ingr, arguments): return ingr @staticmethod - def scale_between_0_and_1(values): + def normalize_by_max_value(values): """ - Scale values between 0 and 1 + Normalize values by their maximum value """ max_value = numpy.nanmax(values) return (values / max_value) if max_value != 0 else values @@ -132,7 +132,7 @@ def get_combined_gradient_weight(gradient_list, gradient_weights=None): weight_list = numpy.zeros((len(gradient_list), len(gradient_list[0].weight))) for i in range(len(gradient_list)): - weight_list[i] = Gradient.scale_between_0_and_1(gradient_list[i].weight) + weight_list[i] = Gradient.normalize_by_max_value(gradient_list[i].weight) if isinstance(gradient_weights, dict): total = sum(gradient_weights.values()) @@ -142,7 +142,7 @@ def get_combined_gradient_weight(gradient_list, gradient_weights=None): ] combined_weight = numpy.average(weight_list, axis=0, weights=gradient_weights) - combined_weight = Gradient.scale_between_0_and_1(combined_weight) + combined_weight = Gradient.normalize_by_max_value(combined_weight) return combined_weight @@ -166,7 +166,7 @@ def pick_point_from_weight(weight, points): """ weights_to_use = numpy.take(weight, points) weights_to_use[numpy.isnan(weights_to_use)] = 0 - weights_to_use = Gradient.scale_between_0_and_1(weights_to_use) + weights_to_use = Gradient.normalize_by_max_value(weights_to_use) point_probabilities = weights_to_use / numpy.sum(weights_to_use) @@ -336,7 +336,7 @@ def build_uniform_weight_map(self, master_grid_positions): def set_weights_by_mode(self): - self.scaled_distances = Gradient.scale_between_0_and_1(self.distances) + self.scaled_distances = Gradient.normalize_by_max_value(self.distances) if (numpy.nanmax(self.scaled_distances) > 1.0) or ( numpy.nanmin(self.scaled_distances) < 0.0 @@ -366,7 +366,7 @@ def set_weights_by_mode(self): raise ValueError(f"Unknown weight mode: {self.weight_mode}") # normalize the weight - self.weight = Gradient.scale_between_0_and_1(self.weight) + self.weight = Gradient.normalize_by_max_value(self.weight) if (numpy.nanmax(self.weight) > 1.0) or (numpy.nanmin(self.weight) < 0.0): raise ValueError( @@ -511,7 +511,7 @@ def create_voxelization(self, image_writer): ) if channel_values is None: continue - normalized_values = Gradient.scale_between_0_and_1(channel_values) + normalized_values = Gradient.normalize_by_max_value(channel_values) reshaped_values = numpy.reshape( normalized_values, image_writer.image_size, order="F" ) From f69f9e381013717ed55c24cde8eea991dcf2c3dc Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 6 Nov 2025 15:14:54 -0800 Subject: [PATCH 19/23] Use uniform weight if `decay_length` is 0 --- cellpack/autopack/Gradient.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index 6d0e2605b..5db5eba48 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -359,9 +359,12 @@ def set_weights_by_mode(self): "power" ] elif self.weight_mode == "exponential": - self.weight = numpy.exp( - -self.scaled_distances / self.weight_mode_settings["decay_length"] - ) + if self.weight_mode_settings["decay_length"] == 0: + self.weight = numpy.ones(len(self.scaled_distances)) + else: + self.weight = numpy.exp( + -self.scaled_distances / self.weight_mode_settings["decay_length"] + ) else: raise ValueError(f"Unknown weight mode: {self.weight_mode}") From c364483bde3d41621b7a3d6c980e1d0b74cb3176 Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 6 Nov 2025 15:15:42 -0800 Subject: [PATCH 20/23] Linting updates --- .gitignore | 1 - cellpack/autopack/Environment.py | 4 ---- cellpack/bin/pack.py | 2 -- cellpack/bin/validate.py | 9 +++++---- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 1b342013d..cd7fd47a4 100644 --- a/.gitignore +++ b/.gitignore @@ -87,7 +87,6 @@ dask-worker-space .env # virtualenv -**venv** .venv venv/ ENV/ diff --git a/cellpack/autopack/Environment.py b/cellpack/autopack/Environment.py index fa7937cb9..101dce8f5 100644 --- a/cellpack/autopack/Environment.py +++ b/cellpack/autopack/Environment.py @@ -853,10 +853,6 @@ def restore_grids_from_pickle(self, grid_file_path): comp_objs = [] mesh_store_objs = [] - grid_objs = [] - comp_objs = [] - mesh_store_objs = [] - with open(grid_file_path, "rb") as file_obj: while True: try: diff --git a/cellpack/bin/pack.py b/cellpack/bin/pack.py index defa3615d..4b4a23bba 100644 --- a/cellpack/bin/pack.py +++ b/cellpack/bin/pack.py @@ -1,4 +1,3 @@ -import json import logging import logging.config import os @@ -6,7 +5,6 @@ from pathlib import Path import fire -from pydantic import ValidationError from cellpack import autopack from cellpack.autopack import upy diff --git a/cellpack/bin/validate.py b/cellpack/bin/validate.py index f4ecdf8a1..32865a00a 100644 --- a/cellpack/bin/validate.py +++ b/cellpack/bin/validate.py @@ -1,11 +1,11 @@ -import json import logging import logging.config -import fire from pathlib import Path -from cellpack.autopack.loaders.recipe_loader import RecipeLoader +import fire + from cellpack.autopack.interface_objects.database_ids import DATABASE_IDS +from cellpack.autopack.loaders.recipe_loader import RecipeLoader ############################################################################### log_file_path = Path(__file__).parent.parent / "logging.conf" @@ -36,7 +36,8 @@ def validate(recipe_path): "Remote database not initialized. Please set up credentials for the database." ) log.error( - "See: https://github.com/mesoscope/cellpack?tab=readme-ov-file#introduction-to-remote-databases" + "See: https://github.com/mesoscope/cellpack?tab=readme-ov-file" + "#introduction-to-remote-databases" ) else: log.error(f"Error loading recipe: {e}") From d241863b60ad3b9cb9175397018ffcc8c77dba3e Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 6 Nov 2025 15:32:23 -0800 Subject: [PATCH 21/23] Update validation for decay length --- cellpack/autopack/validation/recipe_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellpack/autopack/validation/recipe_models.py b/cellpack/autopack/validation/recipe_models.py index fcfc3397e..5c50d9efa 100644 --- a/cellpack/autopack/validation/recipe_models.py +++ b/cellpack/autopack/validation/recipe_models.py @@ -134,7 +134,7 @@ class WeightModeOptions(str, Enum): class WeightModeSettings(BaseModel): - decay_length: Optional[float] = Field(None, gt=0) + decay_length: Optional[float] = Field(None, ge=0) power: Optional[float] = Field(None, gt=0) From c708036d9b93d9085a55de7a9c1bf681ddedeae9 Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 6 Nov 2025 15:44:00 -0800 Subject: [PATCH 22/23] Update validation for multiple gradients --- cellpack/autopack/validation/recipe_models.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cellpack/autopack/validation/recipe_models.py b/cellpack/autopack/validation/recipe_models.py index 5c50d9efa..d4ffa603b 100644 --- a/cellpack/autopack/validation/recipe_models.py +++ b/cellpack/autopack/validation/recipe_models.py @@ -50,6 +50,7 @@ class GradientMode(str, Enum): VECTOR = "vector" RADIAL = "radial" SURFACE = "surface" + UNIFORM = "uniform" class WeightMode(str, Enum): @@ -284,7 +285,7 @@ class RecipeObject(BaseModel): partners: Optional[Union[List[Partner], Dict[str, Any]]] = None # Gradient field supports multiple formats: # - str: Simple reference to gradient name (standard format) - # - List[str]: List of gradient names (for multiple gradients) + # - List[str] OR dict[str, dict[str, Any]]: List of gradient names (for multiple gradients) # - RecipeGradient: Full gradient definition (for unnested Firebase recipes) # - List[RecipeGradient]: List of full gradient definitions (for unnested Firebase recipes) # @@ -294,7 +295,13 @@ class RecipeObject(BaseModel): # Unnested Firebase: {"name": "gradient_name", "mode": "surface", ...} # Converted Firebase list: [{"name": "grad1", "mode": "X"}, {"name": "grad2", "mode": "Y"}] gradient: Optional[ - Union[str, List[str], "RecipeGradient", List["RecipeGradient"]] + Union[ + str, + list[str], + dict[str, dict[str, Any]], + "RecipeGradient", + list["RecipeGradient"], + ] ] = None weight: Optional[float] = Field(None, ge=0) is_attractor: Optional[bool] = None From 5f63605b2dbd46eb956b015e9c2bc9fa0c894452 Mon Sep 17 00:00:00 2001 From: mogres Date: Thu, 6 Nov 2025 15:44:10 -0800 Subject: [PATCH 23/23] Add zero decay gradient in test recipe --- cellpack/tests/recipes/v2/test_gradient.json | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cellpack/tests/recipes/v2/test_gradient.json b/cellpack/tests/recipes/v2/test_gradient.json index 5632eefda..45bb727d0 100644 --- a/cellpack/tests/recipes/v2/test_gradient.json +++ b/cellpack/tests/recipes/v2/test_gradient.json @@ -34,6 +34,28 @@ ] } }, + "zero_decay": { + "mode": "radial", + "description": "Radial gradient with zero decay", + "weight_mode": "exponential", + "pick_mode": "rnd", + "weight_mode_settings": { + "decay_length": 0.0 + }, + "mode_settings": { + "direction": [ + 0, + 0, + 0 + ], + "radius": 150, + "center": [ + 0, + 0, + 0 + ] + } + }, "vector_gradient": { "mode": "vector", "description": "Gradient away from the plane formed by center and vector",