From f09d574832696671d5e6444cd7de18449180303b Mon Sep 17 00:00:00 2001 From: Saurabh Mogre Date: Tue, 25 Jun 2024 14:44:08 -0700 Subject: [PATCH] Add methods and recipe to use multiple gradients on a single ingredient --- cellpack/autopack/Analysis.py | 9 +- cellpack/autopack/Environment.py | 19 +++- cellpack/autopack/Gradient.py | 75 ++++++++++-- .../test_grid_plot_config.json | 9 ++ .../packing-configs/test_mesh_config.json | 18 --- .../recipes/v2/test_combined_gradient.json | 107 ++++++++++++++++++ 6 files changed, 204 insertions(+), 33 deletions(-) create mode 100644 cellpack/tests/packing-configs/test_grid_plot_config.json delete mode 100644 cellpack/tests/packing-configs/test_mesh_config.json create mode 100644 cellpack/tests/recipes/v2/test_combined_gradient.json diff --git a/cellpack/autopack/Analysis.py b/cellpack/autopack/Analysis.py index 5a219734c..4906c4d40 100644 --- a/cellpack/autopack/Analysis.py +++ b/cellpack/autopack/Analysis.py @@ -828,9 +828,12 @@ def update_distance_distribution_dictionaries( get_angles = False if ingr.packing_mode == "gradient" and self.env.use_gradient: - self.center = center = self.env.gradients[ingr.gradient].mode_settings.get( - "center", center - ) + if not isinstance(ingr.gradient, list): + self.center = center = self.env.gradients[ + ingr.gradient + ].mode_settings.get("center", center) + else: + self.center = center get_angles = True # get angles wrt gradient diff --git a/cellpack/autopack/Environment.py b/cellpack/autopack/Environment.py index 13048cfa3..3e56f4080 100644 --- a/cellpack/autopack/Environment.py +++ b/cellpack/autopack/Environment.py @@ -1700,7 +1700,24 @@ def getPointToDrop( # get the most probable point using the gradient # use the gradient weighted map and get mot probabl point self.log.info("pick point from gradients %d", (len(allIngrPts))) - ptInd = self.gradients[ingr.gradient].pickPoint(allIngrPts) + if isinstance(ingr.gradient, list) and len(ingr.gradient) > 1: + if not hasattr(ingr, "combined_weight"): + gradient_list = [ + gradient + for gradient_name, gradient in self.gradients.items() + if gradient_name in ingr.gradient + ] + combined_weight = Gradient.get_combined_gradient_weight( + gradient_list + ) + ingr.combined_weight = combined_weight + + ptInd = Gradient.pick_point_from_weight( + ingr.combined_weight, allIngrPts + ) + + else: + ptInd = self.gradients[ingr.gradient].pickPoint(allIngrPts) else: # pick a point randomly among free points # random or uniform? diff --git a/cellpack/autopack/Gradient.py b/cellpack/autopack/Gradient.py index e33ae5067..473c6281c 100644 --- a/cellpack/autopack/Gradient.py +++ b/cellpack/autopack/Gradient.py @@ -91,6 +91,67 @@ def __init__(self, gradient_data): self.function = self.defaultFunction # lambda ? + @staticmethod + def scale_between_0_and_1(values): + """ + Scale values between 0 and 1 + """ + max_value = numpy.nanmax(values) + min_value = numpy.nanmin(values) + return (values - min_value) / (max_value - min_value) + + @staticmethod + def get_combined_gradient_weight(gradient_list): + """ + Combine the gradient weights + + Parameters + ---------- + gradient_list: list + list of gradient objects + + Returns + ---------- + numpy.ndarray + the combined gradient weight + """ + 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) + + combined_weight = numpy.mean(weight_list, axis=0) + combined_weight = Gradient.scale_between_0_and_1(combined_weight) + + return combined_weight + + @staticmethod + def pick_point_from_weight(weight, points): + """ + Picks a point from a list of points according to the given weight + + Parameters + ---------- + weight: numpy.ndarray + the weight of each point + + points: numpy.ndarray + list of grid point indices + + Returns + ---------- + int + the index of the picked point + """ + weights_to_use = numpy.take(weight, points) + weights_to_use = Gradient.scale_between_0_and_1(weights_to_use) + weights_to_use[numpy.isnan(weights_to_use)] = 0 + + point_probabilities = weights_to_use / numpy.sum(weights_to_use) + + point = numpy.random.choice(points, p=point_probabilities) + + return point + def get_center(self): """get the center of the gradient grid""" center = [0.0, 0.0, 0.0] @@ -113,14 +174,6 @@ def normalize_vector(self, vector): """ return vector / numpy.linalg.norm(vector) - def get_normalized_values(self, values): - """ - Scale values between 0 and 1 - """ - max_value = numpy.nanmax(values) - min_value = numpy.nanmin(values) - return (values - min_value) / (max_value - min_value) - def pickPoint(self, listPts): """ pick next random point according to the chosen function @@ -205,7 +258,7 @@ def build_axis_weight_map(self, bb, master_grid_positions): def set_weights_by_mode(self): - self.scaled_distances = self.get_normalized_values(self.distances) + self.scaled_distances = Gradient.scale_between_0_and_1(self.distances) if (numpy.nanmax(self.scaled_distances) > 1.0) or ( numpy.nanmin(self.scaled_distances) < 0.0 @@ -232,7 +285,7 @@ def set_weights_by_mode(self): -self.scaled_distances / self.weight_mode_settings["decay_length"] ) # normalize the weight - self.weight = self.get_normalized_values(self.weight) + self.weight = Gradient.scale_between_0_and_1(self.weight) if (numpy.nanmax(self.weight) > 1.0) or (numpy.nanmin(self.weight) < 0.0): raise ValueError( @@ -377,7 +430,7 @@ def create_voxelization(self, image_writer): ) if channel_values is None: continue - normalized_values = self.get_normalized_values(channel_values) + normalized_values = Gradient.scale_between_0_and_1(channel_values) reshaped_values = numpy.reshape( normalized_values, image_writer.image_size, order="F" ) diff --git a/cellpack/tests/packing-configs/test_grid_plot_config.json b/cellpack/tests/packing-configs/test_grid_plot_config.json new file mode 100644 index 000000000..d304f69b0 --- /dev/null +++ b/cellpack/tests/packing-configs/test_grid_plot_config.json @@ -0,0 +1,9 @@ +{ + "name": "show_grid_plot", + "out": "cellpack/tests/outputs/", + "load_from_grid_file": false, + "overwrite_place_method": true, + "place_method": "spheresSST", + "save_analyze_result": true, + "show_grid_plot": true +} \ No newline at end of file diff --git a/cellpack/tests/packing-configs/test_mesh_config.json b/cellpack/tests/packing-configs/test_mesh_config.json deleted file mode 100644 index 5f8f77972..000000000 --- a/cellpack/tests/packing-configs/test_mesh_config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "test_mesh_config", - "format": "simularium", - "inner_grid_method": "trimesh", - "live_packing": false, - "ordered_packing": false, - "out": "cellpack/tests/outputs/", - "overwrite_place_method": true, - "place_method": "spheresSST", - "save_analyze_result": true, - "load_from_grid_file": false, - "show_progress_bar": true, - "show_grid_plot": true, - "spacing": null, - "parallel": false, - "use_periodicity": false, - "show_sphere_trees": true -} \ No newline at end of file diff --git a/cellpack/tests/recipes/v2/test_combined_gradient.json b/cellpack/tests/recipes/v2/test_combined_gradient.json new file mode 100644 index 000000000..e760a582a --- /dev/null +++ b/cellpack/tests/recipes/v2/test_combined_gradient.json @@ -0,0 +1,107 @@ +{ + "version": "1.0.0", + "format_version": "2.0", + "name": "test_combined_gradient", + "bounding_box": [ + [ + 0, + 0, + 0 + ], + [ + 1000, + 1000, + 1 + ] + ], + "gradients": { + "X_gradient": { + "description": "X gradient", + "mode": "X", + "pick_mode": "rnd", + "weight_mode": "exponential", + "weight_mode_settings": { + "decay_length": 0.3 + } + }, + "Y_gradient": { + "description": "Y gradient", + "mode": "Y", + "pick_mode": "rnd", + "weight_mode": "exponential", + "weight_mode_settings": { + "decay_length": 0.3 + } + } + }, + "objects": { + "base": { + "jitter_attempts": 10, + "orient_bias_range": [ + -3.1415927, + 3.1415927 + ], + "rotation_range": 6.2831, + "cutoff_boundary": 0, + "max_jitter": [ + 1, + 1, + 0 + ], + "perturb_axis_amplitude": 0.1, + "packing_mode": "random", + "principal_vector": [ + 0, + 0, + 1 + ], + "rejection_threshold": 50, + "place_method": "spheresSST", + "cutoff_surface": 42, + "rotation_axis": [ + 0, + 0, + 1 + ], + "available_regions": { + "interior": {}, + "surface": {}, + "outer_leaflet": {}, + "inner_leaflet": {} + } + }, + "sphere_25": { + "type": "single_sphere", + "inherit": "base", + "color": [ + 0.5, + 0.5, + 0.5 + ], + "radius": 25, + "max_jitter": [ + 1, + 1, + 0 + ], + "packing_mode": "gradient", + "gradient": [ + "X_gradient", + "Y_gradient" + ] + } + }, + "composition": { + "space": { + "regions": { + "interior": [ + "A" + ] + } + }, + "A": { + "object": "sphere_25", + "count": 500 + } + } +} \ No newline at end of file