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 all 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 |
|---|---|---|
|
|
@@ -341,6 +341,11 @@ def _default_registry(self): | |
| _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, | ||
|
|
@@ -102,30 +103,38 @@ class Scope(Recorder): | |
| rho_rel_generator : Callable[[int], Float[np.ndarray, "n"], optional | ||
| A function assigning expression levels. Takes n as an arg, outputs float array. | ||
| ``lambda n: np.ones(n)`` by default. | ||
| focus_depth : Quantity, optional | ||
| The depth of the focal plane, by default that of the scope. | ||
| soma_radius : Quantity, optional | ||
| The radius of the soma of the neuron, by default that of the scope. | ||
| Used to compute noise focus factor, since smaller ROIs will have | ||
| a noisier distribution of fluorescence, averaged over fewer pixels. | ||
| focus_depth : Quantity, optional | ||
| The depth of the focal plane, by default that of the scope. | ||
| soma_radius : Quantity, optional | ||
| The radius of the soma of the neuron, by default that of the scope. | ||
| Used to compute noise focus factor, since smaller ROIs will have | ||
| a noisier distribution of fluorescence, averaged over fewer pixels. | ||
| """ | ||
|
|
||
| sensor: Sensor = field() | ||
| img_width: Quantity = field() | ||
| """The width (diameter) of the (circular) image captured by the microscope. | ||
| Specified in distance units.""" | ||
|
|
||
| focus_depth: Quantity = None | ||
| """The depth of the focal plane, with distance units""" | ||
|
|
||
| location: Quantity = [0, 0, 0] * mm | ||
| """Location of the objective lens.""" | ||
|
|
||
| direction: Float[np.ndarray, "3"] = field( | ||
| default=(0, 0, 1), converter=normalize_coords | ||
| ) | ||
| """Direction in which the microscope is pointing. | ||
| By default straight down (`+z` direction)""" | ||
|
|
||
| soma_radius: Quantity = field(default=10 * um) | ||
| """Assumed radius of neurons, used to compute noise focus factor. | ||
| Smaller neurons have noisier signals.""" | ||
|
|
||
| t_flyback: Quantity = field(default=0 * ms, kw_only=True) | ||
| """Dead time per scan cycle for the scanning mirror to reset (flyback).""" | ||
|
|
||
| snr_cutoff: float = field(default=1) | ||
| """SNR below which neurons are discarded. | ||
| Applied only when focus_depth is not None""" | ||
|
|
@@ -157,6 +166,9 @@ 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""" | ||
|
|
||
| @property | ||
| def n(self) -> int: | ||
| """Number of imaged ROIs""" | ||
|
|
@@ -314,6 +326,10 @@ 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, self.soma_radius | ||
| ) | ||
|
|
||
| def i_targets_for_neuron_group(self, neuron_group): | ||
| """can handle multiple injections into same ng""" | ||
|
|
@@ -451,3 +467,27 @@ 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 | ||
| effective_scan_time = (1 / pulse_freq) * second - self.t_flyback | ||
| pulse_width = spot_area / img_width_area * effective_scan_time | ||
| 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_pulsed=True, | ||
| pulse_width=pulse_width, | ||
| ) | ||
| return self.imaging_light | ||
|
|
||
| # 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, self.soma_radius) | ||
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