Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raster Scanning Code #55

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
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
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
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
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
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


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
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
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