Repository navigation
Conversation
| factory=list, repr=False, init=False | ||
| ) | ||
| """relative expression levels of neurons selected from each injection""" | ||
| Registry: DeviceInteractionRegistry = None |
There was a problem hiding this comment.
Are you sure we need to store the registry when we can just do registry_for_sim(self.sim)?
| 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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
the Scope should optionally have an imaging_light object, and the img_width and scan_freq would be updated specific to that light
|
|
||
| raster_fov: int = field(default=500 * 1e-6 * meter, kw_only=True) | ||
|
|
||
| raster_enable: int = field(default=0, kw_only=True) |
There was a problem hiding this comment.
again, these would be at the level of individual lights, not the whole registry
There was a problem hiding this comment.
this is still the case. These would be Brian state variables inside the Light neuron group
| wavelength: Quantity = field(default=473 * nmeter, kw_only=True) | ||
| """light wavelength with unit (usually nmeter)""" | ||
|
|
||
| scan_freq : int = field(default=30, kw_only=True) |
There was a problem hiding this comment.
I'd create a subclass RasterScanningLight to hide the scan_freq details from the typical Light object
| 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
like you've already done with update_fov but can include these other variables too
kjohnsen
left a comment
There was a problem hiding this comment.
Looks pretty good! Besides the comments below, as I was about to test it (with the script at the bottom that plots all the action spectra), it occurred to me it's getting to be too much to check just by looking at the numbers. I think we need a new tutorial notebook that goes over all the opsins that will serve two puposes: (1) quickly introduce the user to all the options and (2) let us visually inspect that all the opsins are behaving as expected. Can you create a docs/tutorials/opsin_selection.ipynb notebook where we plot 4 things for every opsin?
- The 1P action spectrum,
- the 2P action spectrum,
- the impulse response (the photocurrent after a quick (e.g., 5 ms), strong pulse),
- and the pulse response (what the photocurrent looks like with prolonged stimulation (should see peak, then back down to plateau)
Since there are now too many opsins to visualize all at once, can you break this into groups? For example, plot all the ChR2 variants together, all the Chrimson variants together, all the inhibitory pumps together, all the ChRmine variants together?
There was a problem hiding this comment.
can we remove these? we should add **/.DS_Store to .gitignore
| "metadata": { | ||
| "kernelspec": { | ||
| "display_name": "python3", | ||
| "display_name": ".venv", |
There was a problem hiding this comment.
no need to commit this notebook if you didn't change it. Also, if you did change the notebook, use nbdev clean to get rid of stuff like this before committing
| (490, 0.92), | ||
| (498, 1), | ||
| (516, 0.84), | ||
| (507, 0.90), |
There was a problem hiding this comment.
here's another place the spectrum is out of order. For legibility and reproducibility, it should be in order, but I remembered I actually should have already accounted for this in light_dependence.py, line 125...don't know why I'm getting an error. Could you add a unit test for this? I guess it would be a new file tests/light/test_light_dependence.py
kjohnsen
left a comment
There was a problem hiding this comment.
The overall pattern of creating a light from the scope looks good! Just time to put it all together, make some tweaks, and most importantly, get it working in an end-to-end example and in unit tests
| def init_for_simulator(self, sim: CLSimulator) -> None: | ||
| registry = registry_for_sim(sim) | ||
| registry.init_register_light(self) | ||
| """Test Code for setter method""" |
There was a problem hiding this comment.
I assume you'd move this to test_scope.py?
… default 1p/2p ratio in opsin library to be average of other opsin ratios.
…n group, fix registry rebuild, wire pulse_stagger, add unit tests
… default 1p/2p ratio in opsin library
kjohnsen
left a comment
There was a problem hiding this comment.
Good progress. Quite a bit of changes to make though. Summary of the main architectural change:
Variables that live on scope (attrs fields)
img_widtht_flyback
properties/setters on scope
scan_freq→pulse_freqon light- which in turn is property/setter to light.source neurons
dwell_time: read-only, i.e., property only, not a setter →light.pulse_width- which in turn is property/setter to
light.source.pulse_width
- which in turn is property/setter to
properties/setters on light, pointing to light.source Brian variables
- we don't expose these to the user on scope since they'd always be True
pulse_staggeris_scanning(rename tois_pulsed)
- these get called from different names on scope, as noted above
pulse_widthpulse_freq
For user-friendliness, since there'd no longer be fields on Scope with scanning variables scan_freq or t_flyback (I guess it's just those two, right?), we'd make these more discoverable to the user by making them documented keyword arguments of the inject_imaging_light_for_targets() function. img_width would already be defined, is_pulsed would automatically be true, and dwell_time and pulse_stagger would be computed automatically.
Now I was hoping to let the Brian light.source variables and equations be the single source of truth, but we have a mix of variables defined on the scope (like img_width) and ones defined on the light. So we do need something to keep the light variables in sync when img_width, t_flyback, or scan_freq are changed on scope.
To get that setter behavior with attrs fields, I recommend using on_setattr instead of the _field style you used:
import attrs
def sync_nested_c(instance, attrib, new_value):
if hasattr(instance, "b") and hasattr(instance.b, "c"):
instance.b.c.att = new_value
return new_value
@attrs.define(on_setattr=sync_nested_c)
class Container:
att: str = attrs.field(
converter=str.strip,
validator=attrs.validators.instance_of(str),
)
b: 'Containee'I'd write a single function that computes dwell time, effective power, etc., and updates it all on the light (which in turn uses its setters to update on neurons) and use it with on_setattr as well as setters for the non-scope variables.
| self.focus_coords_per_injct.append(focus_coords) | ||
| self.rho_rel_per_injct.append(rho_rel) | ||
| registry_for_sim(self.sim).update_fov(self.img_width) | ||
| if self.imaging_light is not None: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
|
|
||
| # Method for optional raster scanning | ||
| def create_imaging_light(self): | ||
| def create_imaging_light(self, wavelength=920e-9 * meter, pulse_freq=30): |
There was a problem hiding this comment.
I think we said we wanted to keep the light tucked away, controlled by scope. So probably inject_imaging_light_for_targets() instead, and inject directly here
There was a problem hiding this comment.
The Gemini chat talks about how to calculate the duty cycle/dwell time more accurately
| """light wavelength with unit (usually nmeter)""" | ||
|
|
||
| scan_freq : int = field(default=30, kw_only=True) | ||
| _pulse_freq: float = field(default=30, kw_only=True, alias="pulse_freq") |
There was a problem hiding this comment.
rather than store these as fields on the scope, let's let them be purely derived from the Light. so in the @property, it'd be sth like return self.imaging_light.source...
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 source neurons that instantiate the light, so another layer of properties where needed)
There was a problem hiding this comment.
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
| sim.inject(light, ng) | ||
| syn = list(sim.registry.light_prop_syns.values())[0] | ||
| print(list(syn.variables.keys())) | ||
| scope.is_scanning = True |
There was a problem hiding this comment.
wouldn't want setter here; would always use scanning 2p light
| if not is_scanning: | ||
| return | ||
|
|
||
| # check at multiple timepoints across one scan period |
There was a problem hiding this comment.
this doesn't test that the actual package code is working, but rather that the equations here are working. need to actually run a mini-simulation and check results
| src = sim.registry.source_for_light(light) | ||
| print(list(src.variables.keys())) | ||
|
|
||
| light.pulse_freq = 50 |
There was a problem hiding this comment.
these should probably be tested in test_light.py
with the changes accounting for dwell time, this tests will be trickier. will need to set scan_freq on scope. then dwell time will be computed from img width, flyback, soma radius, etc. to then set pulse_width, like mentioned previously
| from cleo.opto import Opsin, chr2_4s, vfchrimson_4s | ||
| from cleo.registry import registry_for_sim | ||
| from cleo.imaging.scope import Scope | ||
|
|
There was a problem hiding this comment.
remember to use ruff formatting; should automatically sort/organize imports
|
|
||
| # Cython debug symbols | ||
| cython_debug/ No newline at end of file | ||
| cython_debug/**/.DS_Store |
There was a problem hiding this comment.
only need the latter one; will ignore anywhere
…moved registry_for_sim lookup
…s_pulsed; add soma_radius field
Uploaded the code for raster scanning.
Since there's only one registry for simulation, the flag might not work at this moment