Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions cleo/imaging/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from cleo.base import Recorder

from cleo.registry import DeviceInteractionRegistry, registry_for_sim
from cleo.imaging.sensors import Sensor
from cleo.coords import coords_from_ng
from cleo.utilities import normalize_coords, rng
Expand Down Expand Up @@ -83,6 +84,10 @@ def target_neurons_in_plane(

return i_targets, noise_focus_factor[i_targets], coords_on_plane[i_targets]

'''
def fov_change_observation(instance, attribute, value):
instance.Registry.update_fov(value)
'''

@define(eq=False)
class Scope(Recorder):
Expand Down Expand Up @@ -146,6 +151,7 @@ class Scope(Recorder):
factory=list, repr=False, init=False
)
"""relative expression levels of neurons selected from each injection"""
Registry: DeviceInteractionRegistry = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we need to store the registry when we can just do registry_for_sim(self.sim)?


@property
def n(self) -> int:
Expand Down Expand Up @@ -291,6 +297,8 @@ def connect_to_neuron_group(self, neuron_group: NeuronGroup, **kwparams) -> None
self.sigma_per_injct.append(sigma_noise)
self.focus_coords_per_injct.append(focus_coords)
self.rho_rel_per_injct.append(rho_rel)
self.Registry = registry_for_sim(self.sim)
self.Registry.update_fov(self.img_width)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this need to be updated on every injection? Also, it can't be right that there's a single update_fov for the whole registry, since this should vary for each light device

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Scope should optionally have an imaging_light object, and the img_width and scan_freq would be updated specific to that light


def i_targets_for_neuron_group(self, neuron_group):
"""can handle multiple injections into same ng"""
Expand Down
2 changes: 2 additions & 0 deletions cleo/light/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ class Light(Stimulator):
wavelength: Quantity = field(default=473 * nmeter, kw_only=True)
"""light wavelength with unit (usually nmeter)"""

scan_freq : int = field(default=30, kw_only=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd create a subclass RasterScanningLight to hide the scan_freq details from the typical Light object


@coords.validator
def _check_coords(self, attribute, value):
if len(value.shape) != 2 or value.shape[1] != 3:
Expand Down
29 changes: 28 additions & 1 deletion cleo/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

from attrs import define, field
from brian2 import NeuronGroup, Subgroup, Synapses
from brian2 import defaultclock
from brian2.units.allunits import joule, kgram, meter, meter2, nmeter, second
from numpy import pi

from cleo.coords import coords_from_ng
from cleo.utilities import brian_safe_name

import warnings


@define(repr=False)
class DeviceInteractionRegistry:
Expand Down Expand Up @@ -46,11 +50,21 @@ class DeviceInteractionRegistry:
"""Set of (light, light-dependent device, neuron group) tuples representing
previously created connections."""

raster_fov: int = field(default=500 * 1e-6 * meter, kw_only=True)

raster_enable: int = field(default=0, kw_only=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, these would be at the level of individual lights, not the whole registry

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still the case. These would be Brian state variables inside the Light neuron group


light_prop_model = """
T : 1
scale : 1
epsilon : 1
Ephoton : joule
Irr_post = epsilon * T * Irr0_pre : watt/meter**2 (summed)
raster_enable : 1
scan_period : second
raster_dwell_time: second
Irr_norm = epsilon * T * Irr0_pre : watt/meter**2
Irr_raster = epsilon * T * Irr0_pre * int(((t + scan_period * (i/N)) % (scan_period)) < (raster_dwell_time * scale * 10)) / (scale * 10): watt/meter**2
Irr_post = Irr_norm * (1 - raster_enable) + Irr_raster * raster_enable : watt/meter**2 (summed)
phi_post = Irr_post / Ephoton : 1/second/meter**2 (summed)
"""
"""Model used in light propagation synapses"""
Expand Down Expand Up @@ -104,6 +118,10 @@ def connect_light_to_ldd_for_ng(
i_source = self.subgroup_idx_for_light[light]
light_prop_syn.epsilon[i_source, :] = epsilon
light_prop_syn.T[i_source, :] = light.transmittance(coords_from_ng(ng)).ravel()
light_prop_syn.scan_period = second / light.scan_freq

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put these in a separate function that you call both here (on injection) and when you change the params on the scope/imaging light (setter functions). like update_raster_scan_params(scope) or something like that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like you've already done with update_fov but can include these other variables too

light_prop_syn.raster_dwell_time = (pi * 10 ** -10 * meter2)/ (pi*(self.raster_fov/2)**2) * second / light.scan_freq
light_prop_syn.scale = 1 + (defaultclock.dt > light_prop_syn.raster_dwell_time).astype(int) * (defaultclock.dt / (light_prop_syn.raster_dwell_time) - 1)
light_prop_syn.raster_enable = self.raster_enable
# fmt: off
# Ephoton = h*c/lambda
light_prop_syn.Ephoton[i_source, :] = (
Expand Down Expand Up @@ -195,6 +213,15 @@ def source_for_light(self, light: "Light") -> Subgroup:
"""Returns the subgroup representing the given light source"""
i = self.subgroup_idx_for_light[light]
return self.light_source_ng[i]

def update_fov(self, new_fov: float):
self.raster_enable = 1
def custom_warning_format(message, category, filename, lineno, line=None):
return f"{category.__name__}: {message}\n"
warnings.formatwarning = custom_warning_format
if self.raster_fov != new_fov:
self.raster_fov = new_fov
warnings.warn("Warning: cleo currently supports use of single scanning fov in simulation, the latest update parameter will be used", UserWarning)


registries: dict["CLSimulator", DeviceInteractionRegistry] = {}
Expand Down