Repository navigation
Raster Scanning Code #55
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
base: master
Are you sure you want to change the base?
Changes from 9 commits
34465d7
25f6828
d9d00ab
edc09d5
3d43c28
0207e8b
701d945
d6b05c3
d25565a
4211207
f99d727
36000f9
fe27bf2
7ddb6e0
97b6b98
998d8b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,4 +146,6 @@ dmypy.json | |
| .pytype/ | ||
|
|
||
| # Cython debug symbols | ||
| cython_debug/ | ||
| cython_debug/**/.DS_Store | ||
|
|
||
| **/.DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -335,6 +335,11 @@ class CLSimulator(NeoExportable): | |
| _processing_net_op: NetworkOperation = field(default=None, init=False, repr=False) | ||
| _net_store_name: str = field(default="cleo default", init=False, repr=False) | ||
|
|
||
| @property | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This API is different now. |
||
| def registry(self): | ||
| """Returns the DeviceInteractionRegistry for this simulator.""" | ||
| return registry_for_sim(self) | ||
|
|
||
| def inject( | ||
| self, device: InterfaceDevice, *neuron_groups: NeuronGroup, **kwparams: Any | ||
| ) -> CLSimulator: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,20 +2,21 @@ | |
|
|
||
| import warnings | ||
| from datetime import datetime | ||
| from typing import Callable | ||
|
|
||
| import matplotlib as mpl | ||
| import neo | ||
| import quantities as pq | ||
| from attrs import define, field, fields | ||
| from brian2 import NeuronGroup, Quantity, Unit, meter, mm, ms, np, um | ||
| from brian2 import NeuronGroup, Quantity, Unit, meter, mm, ms, np, um, second | ||
| from jaxtyping import Float, UInt | ||
| from matplotlib.artist import Artist | ||
| from mpl_toolkits.mplot3d import Axes3D | ||
| from numpy import pi | ||
|
|
||
| from cleo.base import Recorder | ||
| from cleo.coords import coords_from_ng | ||
| from cleo.imaging.sensors import Sensor | ||
| from cleo.light import Light, GaussianEllipsoid | ||
| from cleo.utilities import ( | ||
| analog_signal, | ||
| normalize_coords, | ||
|
|
@@ -157,6 +158,11 @@ class Scope(Recorder): | |
| ) | ||
| """relative expression levels of neurons selected from each injection""" | ||
|
|
||
| imaging_light: Light = field(init=False, repr=False, default=None) | ||
| """raster scanning parameters""" | ||
|
|
||
| _is_scanning: bool = field(default=False, init=False, repr=False) | ||
|
|
||
| @property | ||
| def n(self) -> int: | ||
| """Number of imaged ROIs""" | ||
|
|
@@ -314,6 +320,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) | ||
| if self.imaging_light is not None: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we support separate img_width per injection? I don't think so, right? So the point here is to just to make sure that if the light is new, that img_width gets set properly? In that case, we'd have other variables that need to get set on the light, like frequency, width, power
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, since we are taking the shortcut of asking the user to inject the sensor after injecting into target neurons, I think we'd want to do the same with the light. In which case, we'd never have a light already created when we're injecting the scope into neurons |
||
| self.sim.registry.set_img_width(self.imaging_light, self.img_width) | ||
|
|
||
| def i_targets_for_neuron_group(self, neuron_group): | ||
| """can handle multiple injections into same ng""" | ||
|
|
@@ -451,3 +459,39 @@ def to_neo(self) -> neo.AnalogSignal: | |
| ) | ||
| return signal | ||
| # want ROI coordinates and size in image | ||
|
|
||
| # Method for optional raster scanning | ||
| def create_imaging_light(self, wavelength=920e-9 * meter, pulse_freq=30): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we said we wanted to keep the light tucked away, controlled by scope. So probably
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Gemini chat talks about how to calculate the duty cycle/dwell time more accurately |
||
| spot_area = pi * self.soma_radius**2 | ||
| img_width_area = pi * (self.img_width / 2) ** 2 | ||
| pulse_width = spot_area / img_width_area * (1 / pulse_freq) * second | ||
| self.imaging_light = Light( | ||
| name=f"{self.name}_imlight", | ||
| light_model=GaussianEllipsoid(), | ||
| wavelength=wavelength, # user can pass in wavelength values | ||
| pulse_freq=pulse_freq, # user sets pulse freq when creating imaging light | ||
| pulse_stagger=True, | ||
| is_scanning=True, | ||
| pulse_width=pulse_width, | ||
| ) | ||
| # attach soma_radius for registry use | ||
| self.imaging_light.soma_radius = self.soma_radius | ||
| return self.imaging_light | ||
|
|
||
| # turn raster scanning on/off | ||
| @property | ||
| def is_scanning(self): | ||
| return self._is_scanning | ||
|
|
||
| @is_scanning.setter | ||
|
randismaael marked this conversation as resolved.
Outdated
|
||
| def is_scanning(self, flag: bool): | ||
| self._is_scanning = flag | ||
| if self.imaging_light is not None: | ||
| self.sim.registry.set_is_scanning(self.imaging_light, flag) | ||
|
|
||
| # keep img_width validator and update imaging width | ||
| @img_width.validator | ||
| def _update_img_width(self, attr, value): | ||
| # Make sure create_imaging_light has been called | ||
| if self.imaging_light is not None: | ||
| self.sim.registry.set_img_width(self.imaging_light, value) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| """Contains Light device and propagation models""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import datetime | ||
|
|
@@ -14,13 +15,17 @@ | |
| Subgroup, | ||
| np, | ||
| ) | ||
|
|
||
| from brian2 import ms | ||
|
randismaael marked this conversation as resolved.
Outdated
|
||
|
|
||
| from brian2.units import ( | ||
| Quantity, | ||
| mm, | ||
| mm2, | ||
| mwatt, | ||
| nmeter, | ||
| um, | ||
| second, | ||
| ) | ||
| from jaxtyping import Float | ||
| from matplotlib import colors | ||
|
|
@@ -316,6 +321,42 @@ class Light(Stimulator): | |
| wavelength: Quantity = field(default=473 * nmeter, kw_only=True) | ||
| """light wavelength with unit (usually nmeter)""" | ||
|
|
||
| _pulse_freq: float = field(default=30, kw_only=True, alias="pulse_freq") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rather than store these as fields on the scope, let's let them be purely derived from the Light. so in the So while the interface lives here on scope, the single source of truth would be the light itself (and for that, it will often be the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and remember from the point of view of imaging, we want to call this scan_freq, then under-the-hood what gets changed on the light is pulse_freq and pulse_width. The latter of which is calculated from dwell time, which is calculated from img_width, pulse_freq, soma_radius, and t_flyback |
||
| _pulse_width: Quantity = field(default=0 * ms, kw_only=True, alias="pulse_width") | ||
|
|
||
| is_scanning: bool = field(default=False, kw_only=True) | ||
| _pulse_stagger: bool = field(default=False, kw_only=True, alias="pulse_stagger") | ||
|
|
||
| @property | ||
| def pulse_freq(self): | ||
| return self._pulse_freq | ||
|
|
||
| @pulse_freq.setter | ||
| def pulse_freq(self, hz: float): | ||
| self._pulse_freq = hz | ||
| if self.sim is not None: | ||
| registry_for_sim(self.sim).set_pulse_freq(self, hz) | ||
|
|
||
| @property | ||
| def pulse_width(self): | ||
| return self._pulse_width | ||
|
|
||
| @pulse_width.setter | ||
| def pulse_width(self, width: Quantity): | ||
| self._pulse_width = width | ||
| if self.sim is not None: | ||
| registry_for_sim(self.sim).set_pulse_width(self, width) | ||
|
|
||
| @property | ||
| def pulse_stagger(self): | ||
| return self._pulse_stagger | ||
|
|
||
| @pulse_stagger.setter | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can assume the pulses would always be staggered for imaging right? so we don't need this here. This would need to be an option for Light though, and correctly set from here. |
||
| def pulse_stagger(self, stagger: bool): | ||
| self._pulse_stagger = stagger | ||
| if self.sim is not None: | ||
| registry_for_sim(self.sim).set_pulse_stagger(self, stagger) | ||
|
|
||
| @coords.validator | ||
| def _check_coords(self, attribute, value): | ||
| if len(value.shape) != 2 or value.shape[1] != 3: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only need the latter one; will ignore anywhere