From 3b87588b9e27e70675d0aca01a378f5ae0f534a8 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 12:08:03 +0100 Subject: [PATCH 1/7] set random_seed variable across methods of Field class --- src/vlab4mic/generate/coordinates_field.py | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/vlab4mic/generate/coordinates_field.py b/src/vlab4mic/generate/coordinates_field.py index bef3ae02..33c1d0a2 100644 --- a/src/vlab4mic/generate/coordinates_field.py +++ b/src/vlab4mic/generate/coordinates_field.py @@ -296,6 +296,7 @@ def set_molecules_params( random_positions, random_orientations, random_rotations, + random_seed=None, **kwargs, ): """ @@ -317,11 +318,11 @@ def set_molecules_params( # self.change_number_of_molecules(nMolecules) self.set_molecule_param("nMolecules", nMolecules) if random_positions: - self.generate_random_positions() + self.generate_random_positions(random_seed=random_seed) if random_orientations: - self.generate_random_orientations() + self.generate_random_orientations(random_seed=random_seed) if random_rotations: - self.initialise_random_rotations() + self.initialise_random_rotations(random_seed=random_seed) for key, value in kwargs.items(): self.molecules_params[key] = value @@ -337,7 +338,7 @@ def show_params(self): print(key, ": ", val) # methods to prime molecule positions, orientations... - def generate_random_positions(self): + def generate_random_positions(self, random_seed=None): """ Generate random positions for molecules within the field, optionally enforcing a minimal distance. """ @@ -359,15 +360,15 @@ def generate_random_positions(self): self.molecules_params["relative_positions"] = rand self._gen_abs_from_rel_positions() - def randomise_axial_position(self): - rng = np.random.default_rng() + def randomise_axial_position(self, random_seed=None): + rng = np.random.default_rng(seed=random_seed) nmolecules = self.get_molecule_param("nMolecules") axial_offsets = rng.choice(self.axial_offset, nmolecules, replace=True) for i, zpos in enumerate(axial_offsets): self.molecules_params["absolute_positions"][i][2] = zpos + self.z_offset - def generate_random_orientations(self): + def generate_random_orientations(self, random_seed=None): """ Generate random orientations for all molecules in the field. """ @@ -375,7 +376,7 @@ def generate_random_orientations(self): norientations = self.get_molecule_param("nMolecules") orientations = [] unconstrained = True - rng = np.random.default_rng() + rng = np.random.default_rng(seed=random_seed) xy_orientation_changes = np.zeros((norientations,)) if self.xy_orientations is not None: xy_orientation_changes = rng.choice(self.xy_orientations, norientations, replace=True) @@ -403,12 +404,12 @@ def generate_random_orientations(self): self.set_molecule_param("orientations_planewise", orientations_planewise) self.set_molecule_param("orientations", None) - def initialise_random_rotations(self, rotation_angles: list = None): + def initialise_random_rotations(self, rotation_angles: list = None, random_seed=None): """ Generate random rotations around central axis for all molecules in the field. """ nrotations = self.get_molecule_param("nMolecules") - rng = np.random.default_rng() + rng = np.random.default_rng(seed=random_seed) if self.rotation_angles is None: print("random unconstrained rotations") rotations = rng.integers(360, size=nrotations) @@ -531,7 +532,7 @@ def get_fluorophore_params(self): return self.fluoparams # working with macromolecules - def create_molecules_from_InstanceObject(self, InstancePrototype: LabeledInstance): + def create_molecules_from_InstanceObject(self, InstancePrototype: LabeledInstance, random_seed=None): """ Create molecules in the field from a prototype instance. @@ -550,7 +551,7 @@ def create_molecules_from_InstanceObject(self, InstancePrototype: LabeledInstanc self._set_molecule_minimal_distance(dist=particle_copy.radial_hindance) #self._set_molecule_minimal_distance(dist=min_distance) if self.random_placing: - self.generate_random_positions() + self.generate_random_positions(random_seed=random_seed) self._gen_abs_from_rel_positions() # due to constraints, the actual number of particles might not be reps nmolecules = len(self.molecules_params["absolute_positions"]) @@ -569,9 +570,9 @@ def create_molecules_from_InstanceObject(self, InstancePrototype: LabeledInstanc if self.sample_initial_orientation is not None: self.generate_global_orientation(self.sample_initial_orientation) if self.random_orientations: - self.generate_random_orientations() + self.generate_random_orientations(random_seed=random_seed) if self.random_rotations: - self.initialise_random_rotations() + self.initialise_random_rotations(random_seed=random_seed) # self.relabel_molecules() self.relabel_molecules() From f1d4a3be53749dc0eda5b4bbe94f3548a601d2f9 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 14:20:28 +0100 Subject: [PATCH 2/7] Propagate random seed set at experiment label to downstream initialisation of default_rng --- src/vlab4mic/experiments.py | 4 ++-- src/vlab4mic/generate/coordinates_field.py | 8 +++++--- src/vlab4mic/workflows.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vlab4mic/experiments.py b/src/vlab4mic/experiments.py index e8483360..b5e65bde 100644 --- a/src/vlab4mic/experiments.py +++ b/src/vlab4mic/experiments.py @@ -617,7 +617,7 @@ def _build_coordinate_field( if use_self_particle and self.generators_status("particle"): print("creating field from existing particle") exported_field, fieldobject = field_from_particle( - self.particle, **self.virtualsample_params, **kwargs + self.particle, **self.virtualsample_params, random_seed=self.random_seed, **kwargs ) self.virtualsample_params["minimal_distance"] = ( fieldobject.molecules_params["minimal_distance"] @@ -631,7 +631,7 @@ def _build_coordinate_field( else: # create minimal field fieldobject = coordinates_field.create_min_field( - **self.virtualsample_params, **kwargs + **self.virtualsample_params, random_seed=self.random_seed, **kwargs ) exported_field = fieldobject.export_field() if keep: diff --git a/src/vlab4mic/generate/coordinates_field.py b/src/vlab4mic/generate/coordinates_field.py index 33c1d0a2..60723cfa 100644 --- a/src/vlab4mic/generate/coordinates_field.py +++ b/src/vlab4mic/generate/coordinates_field.py @@ -121,7 +121,7 @@ def init_from_file(self, field_yaml): self.set_molecules_params(**molecules) def create_minimal_field( - self, nmolecules=1, random_placing=False, random_orientations=False, random_rotations=False, **kwargs + self, nmolecules=1, random_placing=False, random_orientations=False, random_rotations=False, random_seed=None, **kwargs ): """ Create a minimal field with a specified number of molecules and placement options. @@ -163,7 +163,7 @@ def create_minimal_field( # if no list was passed, then we will use the default nmolecules parameter # having more than one particle necesarily use random placing self.random_placing = True - self.generate_random_positions() + self.generate_random_positions(random_seed=random_seed) self._gen_abs_from_rel_positions() self.fluorophre_emitters = { fluo_name: self.get_molecule_param("absolute_positions") @@ -174,7 +174,7 @@ def create_minimal_field( self.set_molecule_param("nMolecules", 1) if random_placing: self.random_placing = True - self.generate_random_positions() + self.generate_random_positions(random_seed=random_seed) self._gen_abs_from_rel_positions() point = self.get_molecule_param("absolute_positions") self.fluorophre_emitters = {fluo_name: point.reshape(1, 3)} @@ -944,6 +944,7 @@ def create_min_field( random_orientations=False, random_rotations=False, prints=False, + random_seed=None, **kwargs, ): """ @@ -978,6 +979,7 @@ def create_min_field( random_placing=random_placing, random_orientations=random_orientations, random_rotations=random_rotations, + random_seed=random_seed, **kwargs, ) return coordinates_field diff --git a/src/vlab4mic/workflows.py b/src/vlab4mic/workflows.py index 28251116..9dfaaebb 100644 --- a/src/vlab4mic/workflows.py +++ b/src/vlab4mic/workflows.py @@ -273,7 +273,7 @@ def particle_from_structure( def field_from_particle( - particle: labinstance.LabeledInstance, field_config: str = None, **kwargs + particle: labinstance.LabeledInstance, field_config: str = None, random_seed=None, **kwargs ): """ Create a particle field from an input particle object. @@ -305,7 +305,7 @@ def field_from_particle( # coordinates_field.init_from_file(field_config) else: coordinates_field = create_min_field(**kwargs) - coordinates_field.create_molecules_from_InstanceObject(particle) + coordinates_field.create_molecules_from_InstanceObject(particle, random_seed=random_seed) coordinates_field.construct_static_field() return coordinates_field.export_field(), coordinates_field From e1bc7b2f2fdc0e4c2de177471e728525359677d2 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 15:37:34 +0100 Subject: [PATCH 3/7] update fig1 script to reproduce antibody model with random seed --- examples/article_figures/fig1_hiv_examples.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/article_figures/fig1_hiv_examples.py b/examples/article_figures/fig1_hiv_examples.py index a2ca6f23..58d27e9a 100644 --- a/examples/article_figures/fig1_hiv_examples.py +++ b/examples/article_figures/fig1_hiv_examples.py @@ -114,6 +114,7 @@ central_axis=False, use_dol=True, axis_object = ax, + random_seed=seed ) filename = my_experiment.date_as_string + 'vlab4mic_hiv_antibody.png' From 6da2ecc616e02b4b328e86fa183b29eae7b92966 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 15:38:26 +0100 Subject: [PATCH 4/7] Propagate random seed for showing probe when using DoL --- src/vlab4mic/generate/labelled_instance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vlab4mic/generate/labelled_instance.py b/src/vlab4mic/generate/labelled_instance.py index 085f482a..73b3cae7 100644 --- a/src/vlab4mic/generate/labelled_instance.py +++ b/src/vlab4mic/generate/labelled_instance.py @@ -1001,6 +1001,7 @@ def show_probe( use_dol=False, exact_dol= None, axis_object=None, + random_seed=None, **kwargs, ): """ @@ -1052,6 +1053,7 @@ def show_probe( probe_emitters, center, np.array([0, 0, 0]) ) if dol is not None and use_dol: + rng_ = np.random.default_rng(seed=random_seed) list_reoriented_points = [] max_emitters = centered_emitters.shape[0] emitter_indices = np.arange(max_emitters) @@ -1060,13 +1062,13 @@ def show_probe( selected_emitters = [emitter_indices[x] for x in exact_dol] #selected_emitters = emitter_indices[exact_dol] else: - int_dol = np.random.poisson(lam=dol) + int_dol = rng_.poisson(lam=dol) if int_dol != 0: if int_dol > max_emitters: # use max value selected_emitters = emitter_indices else: - selected_emitters = np.random.choice( + selected_emitters = rng_.choice( emitter_indices, size=int_dol, replace=False ) for se in selected_emitters: From 4e01daf7eb74f1038478a05af2e5c4c0dc833ff9 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 15:45:53 +0100 Subject: [PATCH 5/7] Rename script fig3 output names to match figure panels letters --- examples/article_figures/fig3_abc_panels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/article_figures/fig3_abc_panels.py b/examples/article_figures/fig3_abc_panels.py index 228b997e..81950d64 100644 --- a/examples/article_figures/fig3_abc_panels.py +++ b/examples/article_figures/fig3_abc_panels.py @@ -101,7 +101,7 @@ def create_parameter_grid_as_list(h_positions, v_positions, margin=0.1, min_z = ax.set_xticklabels([]) ax.set_yticklabels([]) -filename = myexperiment2.date_as_string + 'vlab4mic_fig3_panelA.png' +filename = myexperiment2.date_as_string + 'vlab4mic_fig3_panelB.png' filename2 = os.path.join(myexperiment2.output_directory, filename) fig.savefig(filename2,dpi=300, bbox_inches='tight') plt.close() @@ -153,7 +153,7 @@ def create_parameter_grid_as_list(h_positions, v_positions, margin=0.1, min_z = myexperiment2.particle.gen_axis_plot(axis_object=ax4, with_sources=False, source_plotsize=0, source_plotmarker="o", view_init=[90,0,0], xlim=[0,1000], ylim=[0,1000], zlim=[0,600], emitter_plotsize=1) -filename = myexperiment2.date_as_string + 'vlab4mic_fig3_panelB.png' +filename = myexperiment2.date_as_string + 'vlab4mic_fig3_panel_AC.png' filename2 = os.path.join(myexperiment2.output_directory, filename) fig2.savefig(filename2,dpi=300, bbox_inches='tight') plt.close() From 76ee2fcf6cc8542e6000a5facd39e40fd78ab058 Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 16:33:20 +0100 Subject: [PATCH 6/7] Rename output of script for fig3 panel E --- examples/article_figures/fig3_de_panels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/article_figures/fig3_de_panels.py b/examples/article_figures/fig3_de_panels.py index 34ac653a..4a810d29 100644 --- a/examples/article_figures/fig3_de_panels.py +++ b/examples/article_figures/fig3_de_panels.py @@ -46,6 +46,6 @@ axs[3].set_title(title) -filename = os.path.join(sweep_gen.output_directory, 'vlab4mic_fig3B.png') +filename = os.path.join(sweep_gen.output_directory, 'vlab4mic_fig3E.png') fig.savefig(filename, dpi=300, bbox_inches='tight') plt.close() \ No newline at end of file From d3bffdb6b76b4e9c921015879d25b16a027080bf Mon Sep 17 00:00:00 2001 From: Damian Martinez Date: Tue, 9 Jun 2026 16:43:03 +0100 Subject: [PATCH 7/7] Create bit generator for script for vsample generator --- examples/article_figures/figS_image_for_positioning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/article_figures/figS_image_for_positioning.py b/examples/article_figures/figS_image_for_positioning.py index 971a66bf..faf79954 100644 --- a/examples/article_figures/figS_image_for_positioning.py +++ b/examples/article_figures/figS_image_for_positioning.py @@ -45,8 +45,8 @@ plt.close() - -img_mask = np.random.rand(100,100) +rng_ = np.random.default_rng(seed=random_seed) +img_mask = rng_.random([100,100]) p = 0.99 img_mask[img_mask >= p] = 1 img_mask[img_mask < p] = 0