OSCR

An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons.

Code ↔ Paper

19 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 19 matches
  1. [1] § Experimental Section/Methods › Evoked Activity Experiments ↔ src/cali/sqlmodel/_util.py, lines 544–665 · score 0.87 · LED power, pulse duration, stimulation events, optogenetic stimulation, Detected peaks, Peak amplitudes
  2. [2] § Experimental Section/Methods › Peak Detection ↔ src/cali/gui/_analysis_gui.py, lines 790–849 · score 0.82 · minimum peak height, find peaks, F0 traces, calcium transients, scipy, noise
  3. [3] § Experimental Section/Methods › Evoked Activity Experiments ↔ src/cali/sqlmodel/_visualize_experiment.py, lines 260–338 · score 0.78 · stimulation parameters, LED power, evoked activity, pulse duration, stimulation mask, frame
  4. [4] § Results › Quantification of Optically Evoked Activity ↔ src/cali/plot/_single_wells_plots/correlation/_plot_evoked_correlation_synchrony.py, lines 1097–1182 · score 0.76 · Stim median, Pairwise Pearson Correlation, Global median, Stim Periods, Stim Windows, stimulated ROIs
  5. [5] § Experimental Section/Methods › Multi‐Dimensional Acquisition (MDA) Interface ↔ src/micromanager_gui/_widgets/_mm_console.py, lines 49–144 · score 0.75 · micromanager gui, pymmcore plus, Python, useq, MDA, widgets
  6. [6] § Experimental Section/Methods › Peak Detection ↔ src/cali/sqlmodel/_util.py, lines 544–665 · score 0.74 · OASIS package, prominence threshold, peak height, inter, dynamically, denoised
  7. [7] § Results › Modular Acquisition Platform for Calcium Imaging and Optogenetics ↔ src/micromanager_gui/_widgets/_mm_console.py, lines 49–144 · score 0.69 · micromanager gui, pymmcore plus, Python, useq, widgets
  8. [8] § Experimental Section/Methods › Features Extraction ↔ src/cali/analysis/_analysis_runner.py, lines 263–407 · score 0.68 · inter event interval, mask area, peak amplitude, IEI, active, trace
  9. [9] § Results › Quantification of Optically Evoked Activity ↔ src/cali/plot/_single_wells_plots/correlation/_plot_evoked_correlation_synchrony.py, lines 552–685 · score 0.67 · pairwise Pearson correlation, global median, stimulated neurons, synchrony, evoked, stimulus
  10. [10] § Experimental Section/Methods › Statistical Independence and Experimental Replicates ↔ src/cali/plot/_multi_wells_plots/_util.py, lines 391–488 · score 0.62 · biological replicate, technical replicates, hierarchical, nested, ROI, FOVs
  11. [11] § Experimental Section/Methods › Multi‐Dimensional Acquisition (MDA) Interface ↔ src/micromanager_gui/_widgets/_viewers/_mda_viewer/_data_wrappers.py, lines 19–68 · score 0.59 · micromanager gui, pymmcore plus, MDA, widgets
  12. [12] § Results › Integrated Data Exploration and Analysis via Cali ↔ src/cali/sqlmodel/_model.py, lines 751–803 · score 0.58 · custom Cellpose, custom model, Quality, Cyto3, segmentation, Cali
  13. [13] § Experimental Section/Methods › Multi‐Dimensional Acquisition (MDA) Interface ↔ src/micromanager_gui/_engine.py, lines 66–126 · score 0.56 · hardware autofocus devices, MDA, channel, position, GUI
  14. [14] § Results › Modular Acquisition Platform for Calcium Imaging and Optogenetics ↔ src/micromanager_gui/_widgets/_viewers/_mda_viewer/_data_wrappers.py, lines 19–68 · score 0.54 · micromanager gui, pymmcore plus, platform, widgets
  15. [15] § Experimental Section/Methods › Quantitative Evaluation of Segmentation With a Custom Trained Cellpose Model ↔ src/micromanager_gui/_realtime_cellpose_segmentation.py, lines 131–197 · score 0.53 · Cellpose model, pretrained, Cyto3, Segmentation
  16. [16] § Experimental Section/Methods › Quantitative Evaluation of Segmentation With a Custom Trained Cellpose Model ↔ src/cali/sqlmodel/_model.py, lines 751–803 · score 0.52 · Cellpose model, Quality, Cyto3, detections, Segmentation
  17. [17] § Results › Integrated Data Exploration and Analysis via Cali ↔ src/cali/analysis/_fov_metrics.py, lines 1440–1562 · score 0.52 · Pearson correlation matrices, calcium traces, metrics, row, synchrony, denoised
  18. [18] § Experimental Section/Methods › Data Analysis ↔ src/cali/gui/_cali_gui.py, lines 1967–2096 · score 0.51 · cali GUI, plate maps, genotypes, treatment
  19. [19] § Experimental Section/Methods › Compute ΔF/F0 ↔ src/cali/gui/_extraction_gui.py, lines 395–527 · score 0.51 · sliding window, fluorescence F0, baseline, timepoint, traces

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 666 lines · 24 KB · BSD-3-Clause · 2 matches

  1. """Utility functions for cali.sqlmodel database operations.
  2. This module provides helper functions for database operations including:
  3. - Creating database tables
  4. - Loading experiments from database
  5. - Checking analysis settings consistency
  6. """
  7. from __future__ import annotations
  8. from dataclasses import dataclass, replace
  9. from pathlib import Path
  10. from typing import TYPE_CHECKING, Any, TypeVar
  11. from sqlalchemy import text
  12. from sqlalchemy.exc import IntegrityError
  13. from sqlmodel import Session, create_engine, select
  14. from cali._constants import DEFAULT_CALI_DB_NAME
  15. from ._model import Experiment
  16. if TYPE_CHECKING:
  17. from sqlalchemy.engine import Engine
  18. from cali.logger import cali_logger
  19. def migrate_analysis_settings(engine: Engine) -> None:
  20. """Add missing columns to analysis_settings table for existing databases.
  21. This is safe to call multiple times — it only adds columns that don't exist.
  22. """
  23. with engine.connect() as conn:
  24. existing_cols = {
  25. row[1] for row in conn.execute(text("PRAGMA table_info(analysis_settings)"))
  26. }
  27. if not existing_cols:
  28. return # table doesn't exist yet
  29. if "enable_calcium" not in existing_cols:
  30. conn.execute(
  31. text(
  32. "ALTER TABLE analysis_settings "
  33. "ADD COLUMN enable_calcium BOOLEAN DEFAULT 1 NOT NULL"
  34. )
  35. )
  36. if "enable_spikes" not in existing_cols:
  37. conn.execute(
  38. text(
  39. "ALTER TABLE analysis_settings "
  40. "ADD COLUMN enable_spikes BOOLEAN DEFAULT 1 NOT NULL"
  41. )
  42. )
  43. conn.commit()
  44. def create_database_and_tables(engine: Engine) -> None:
  45. """Create all database tables.
  46. Parameters
  47. ----------
  48. engine : sqlalchemy.engine.Engine
  49. Database engine
  50. Example
  51. -------
  52. >>> from sqlmodel import create_engine
  53. >>> from cali.sqlmodel import create_database_and_tables
  54. >>> engine = create_engine("sqlite:///calcium_analysis.db")
  55. >>> create_database_and_tables(engine)
  56. """
  57. from sqlmodel import SQLModel
  58. # Import all models to register them with SQLModel metadata
  59. from ._model import ( # noqa: F401
  60. FOV,
  61. ROI,
  62. AnalysisSettings,
  63. CaliResult,
  64. Condition,
  65. DataAnalysis,
  66. DetectionSettings,
  67. ExtractionSettings,
  68. FOVAnalysis,
  69. Mask,
  70. Plate,
  71. Traces,
  72. Well,
  73. WellCondition,
  74. )
  75. SQLModel.metadata.create_all(engine)
  76. migrate_analysis_settings(engine)
  77. def save_experiment_to_database(
  78. experiment: Experiment,
  79. output_path: Path | str,
  80. *,
  81. database_name: str = DEFAULT_CALI_DB_NAME,
  82. overwrite: bool = False,
  83. echo: bool = False,
  84. ) -> None:
  85. """Save an experiment object tree to a SQLite database.
  86. This function saves the experiment and returns nothing, following SQLModel
  87. best practices of not returning objects to discourage keeping large object
  88. trees in memory. Load the experiment fresh from the database when needed
  89. using load_experiment_from_database().
  90. Parameters
  91. ----------
  92. experiment : Experiment
  93. Experiment object
  94. output_path : Path | str
  95. Output directory to save the database file.
  96. database_name : str, optional
  97. Name of the database file (e.g., "cali.db"). Defaults to "results.cali".
  98. overwrite : bool, optional
  99. Whether to overwrite existing database file, by default False
  100. echo : bool, optional
  101. Whether to enable SQLAlchemy engine echo for debugging, by default False
  102. Example
  103. -------
  104. >>> from pathlib import Path
  105. >>> save_experiment_to_database(exp, overwrite=True)
  106. >>> # Later, load fresh from DB when needed:
  107. >>> db_path = Path(exp.output_path) / exp.database_name
  108. >>> exp = load_experiment_from_database(db_path)
  109. """
  110. # Determine database path
  111. db_name = database_name if database_name is not None else DEFAULT_CALI_DB_NAME
  112. assert db_name is not None # Guaranteed by the check above
  113. if not db_name.endswith(".cali"):
  114. db_name += ".cali"
  115. db_path = Path(output_path) / db_name
  116. # Ensure parent directory exists
  117. db_path.parent.mkdir(parents=True, exist_ok=True)
  118. if overwrite and db_path.exists():
  119. db_path.unlink()
  120. engine = create_engine(
  121. f"sqlite:///{db_path}",
  122. echo=echo,
  123. connect_args={"timeout": 30.0, "check_same_thread": False},
  124. pool_pre_ping=True,
  125. )
  126. create_database_and_tables(engine)
  127. try:
  128. with Session(engine) as session:
  129. # Pre-resolve conditions BEFORE merge to avoid session.merge()
  130. # limitations with link_model many-to-many relationships.
  131. # Safely check if plate is loaded without triggering lazy load
  132. # on detached instance
  133. from sqlalchemy import inspect as sa_inspect
  134. from sqlalchemy import or_
  135. from cali.sqlmodel._model import Condition
  136. insp = sa_inspect(experiment)
  137. plate_loaded = "plate" in insp.dict and insp.dict["plate"] is not None
  138. # Store original well-to-conditions mapping BEFORE merge
  139. well_condition_map: dict[int, list[tuple[str, str]]] = {}
  140. if plate_loaded and experiment.plate is not None:
  141. # Collect all unique (name, condition_type) pairs from all wells
  142. conditions_needed: set[tuple[str, str]] = set()
  143. for idx, well in enumerate(experiment.plate.wells):
  144. # Store this well's condition keys
  145. well_keys = [(c.name, c.condition_type) for c in well.conditions]
  146. well_condition_map[idx] = well_keys
  147. conditions_needed.update(well_keys)
  148. # Batch fetch existing conditions in ONE query
  149. condition_lookup: dict[tuple[str, str], Condition] = {}
  150. if conditions_needed:
  151. or_clauses = [
  152. (Condition.name == name) & (Condition.condition_type == ctype)
  153. for name, ctype in conditions_needed
  154. ]
  155. existing = session.exec(
  156. select(Condition).where(or_(*or_clauses))
  157. ).all()
  158. condition_lookup = {(c.name, c.condition_type): c for c in existing}
  159. # Merge experiment into session first
  160. merged_exp = session.merge(experiment)
  161. # Now fix up conditions on the session-attached wells
  162. if plate_loaded and merged_exp.plate is not None:
  163. for idx, well in enumerate(merged_exp.plate.wells):
  164. # Use original well's condition keys (before merge)
  165. condition_keys = well_condition_map.get(idx, [])
  166. if not condition_keys:
  167. # No conditions for this well, skip it
  168. continue
  169. resolved_conditions: list[Condition] = []
  170. for key in condition_keys:
  171. existing_cond = condition_lookup.get(key)
  172. if existing_cond:
  173. # Use existing condition from DB
  174. resolved_conditions.append(existing_cond)
  175. else:
  176. # Condition doesn't exist - query for it
  177. name, ctype = key
  178. stmt = select(Condition).where(
  179. (Condition.name == name)
  180. & (Condition.condition_type == ctype)
  181. )
  182. cond_in_session = session.exec(stmt).first()
  183. if cond_in_session:
  184. resolved_conditions.append(cond_in_session)
  185. # else: condition not found, skip it
  186. # Assign resolved conditions (replaces whatever merge() set)
  187. if resolved_conditions:
  188. well.conditions = resolved_conditions
  189. session.commit()
  190. # Refresh to get the ID assigned by the database
  191. session.refresh(merged_exp)
  192. # Update the original experiment object with the database ID
  193. experiment.id = merged_exp.id
  194. cali_logger.info(
  195. f"💾 Experiment analysis updated and saved to database at {db_path}."
  196. )
  197. except IntegrityError as e:
  198. cali_logger.error(
  199. f"❌ Failed to save experiment to database. "
  200. f"Integrity constraint violated: {e}"
  201. )
  202. raise
  203. finally:
  204. # Dispose engine to release database connections (Windows compatibility)
  205. engine.dispose(close=True)
  206. def load_experiment_from_database(
  207. db_path: Path | str,
  208. experiment_name: str | None = None,
  209. echo: bool = False,
  210. ) -> Experiment | None:
  211. """Load an experiment from SQLite database with all relationships.
  212. This function loads a complete experiment snapshot for read-only analysis
  213. or display. The returned object is detached from the session (expunged) and
  214. can be used outside the session context.
  215. Parameters
  216. ----------
  217. db_path : Path | str
  218. Path to SQLite database file
  219. experiment_name : str | None, optional
  220. Name of specific experiment to load. If None, loads the first experiment.
  221. echo : bool, optional
  222. Whether to enable SQLAlchemy engine echo for debugging, by default False
  223. Returns
  224. -------
  225. Experiment | None
  226. Loaded experiment with all relationships, or None if not found.
  227. The object is detached (expunged) and can be used outside the session.
  228. Example
  229. -------
  230. >>> from pathlib import Path
  231. >>> # For read-only display/analysis:
  232. >>> exp = load_experiment_from_database("analysis.db", "my_experiment")
  233. >>> if exp:
  234. ... print(f"Loaded {len(exp.plate.wells)} wells")
  235. >>>
  236. >>> # For modifications, use engine + ID pattern instead:
  237. >>> engine = create_engine("sqlite:///analysis.db")
  238. >>> with Session(engine) as session:
  239. ... exp = session.get(Experiment, experiment_id)
  240. ... exp.name = "Updated Name" # Modify within session
  241. ... session.commit() # Save changes
  242. """
  243. from pathlib import Path
  244. from sqlalchemy.exc import OperationalError
  245. from sqlmodel import select
  246. # Check if database file exists
  247. db_path = Path(db_path) if isinstance(db_path, str) else db_path
  248. if not db_path.exists():
  249. return None
  250. # Convert to string for consistency
  251. db_path_str = str(db_path)
  252. engine = create_engine(
  253. f"sqlite:///{db_path_str}",
  254. echo=echo,
  255. connect_args={"timeout": 30.0, "check_same_thread": False},
  256. pool_pre_ping=True,
  257. )
  258. try:
  259. # Use context manager to ensure session is properly closed
  260. with Session(engine, expire_on_commit=False) as session:
  261. # Query for experiment
  262. if experiment_name:
  263. statement = select(Experiment).where(Experiment.name == experiment_name)
  264. else:
  265. statement = select(Experiment)
  266. try:
  267. experiment = session.exec(statement).first()
  268. except OperationalError:
  269. # Database exists but tables don't (corrupted or empty database)
  270. return None
  271. if not experiment:
  272. return None
  273. # Force load all relationships to prevent DetachedInstanceError
  274. _force_load_experiment_relationships(experiment)
  275. # Make the instance independent of the session
  276. session.expunge(experiment)
  277. # Session automatically closed here
  278. return experiment # type: ignore
  279. finally:
  280. # Dispose engine to release database connections (Windows compatibility)
  281. engine.dispose(close=True)
  282. def _force_load_experiment_relationships(experiment: Experiment) -> None:
  283. """Force load all experiment relationships to prevent DetachedInstanceError.
  284. This function eagerly loads all relationships on an experiment object while
  285. the session is still active, ensuring the object can be used outside the session.
  286. Parameters
  287. ----------
  288. experiment : Experiment
  289. The experiment object to load relationships for
  290. """
  291. # Force load ALL relationships deeply while session is still open
  292. # This prevents DetachedInstanceError when accessed later
  293. if experiment.plate:
  294. _ = len(experiment.plate.wells) # Force load wells
  295. for well in experiment.plate.wells:
  296. _ = len(well.conditions) # Force load conditions
  297. _ = len(well.fovs) # Force load fovs
  298. for fov in well.fovs:
  299. _ = len(fov.rois) # Force load rois
  300. for roi in fov.rois:
  301. # Force load all ROI relationships
  302. _ = len(roi.traces_history)
  303. _ = len(roi.data_analysis_history)
  304. _ = roi.roi_mask
  305. def has_fov_analysis(db_path: str | Path, fov_name: str) -> bool:
  306. """Check if a specific FOV has been analyzed by querying database directly.
  307. Directly queries the database to check if the FOV exists and has analyzed ROIs.
  308. Parameters
  309. ----------
  310. db_path : str | Path
  311. Path to the SQLite database file
  312. fov_name : str
  313. Name of the FOV to check (e.g., "B5_0000")
  314. Returns
  315. -------
  316. bool
  317. True if the FOV exists and has analyzed ROIs, False otherwise
  318. Example
  319. -------
  320. >>> from cali.sqlmodel import has_fov_analysis
  321. >>> if has_fov_analysis("analysis.db", "B5_0000"):
  322. ... print("B5_0000 has been analyzed")
  323. """
  324. from sqlmodel import select
  325. from ._model import FOV, ROI, Traces
  326. engine = create_engine(
  327. f"sqlite:///{db_path}",
  328. connect_args={"timeout": 30.0, "check_same_thread": False},
  329. pool_pre_ping=True,
  330. )
  331. try:
  332. with Session(engine) as session:
  333. # Check if this specific FOV has any ROIs with Traces entries
  334. # (which indicates the FOV has been analyzed)
  335. statement = (
  336. select(Traces).join(ROI).join(FOV).where(FOV.name == fov_name).limit(1)
  337. )
  338. result = session.exec(statement).first()
  339. return result is not None
  340. finally:
  341. engine.dispose(close=True)
  342. def has_experiment_analysis(db_path: str | Path) -> bool:
  343. """Check if experiment has any analyzed data by querying database directly.
  344. Directly queries the database to check if any ROIs exist with analysis data.
  345. Parameters
  346. ----------
  347. db_path : str | Path
  348. Path to the SQLite database file
  349. Returns
  350. -------
  351. bool
  352. True if any ROIs have analysis data, False otherwise
  353. Example
  354. -------
  355. >>> from cali.sqlmodel import has_experiment_analysis
  356. >>> if has_experiment_analysis("analysis.db"):
  357. ... print("Experiment has analysis data")
  358. """
  359. from sqlmodel import select
  360. from ._model import Traces
  361. engine = create_engine(
  362. f"sqlite:///{db_path}",
  363. connect_args={"timeout": 30.0, "check_same_thread": False},
  364. pool_pre_ping=True,
  365. )
  366. try:
  367. with Session(engine) as session:
  368. # Check if any Traces entries exist (indicates analysis has been run)
  369. statement = select(Traces).limit(1)
  370. result = session.exec(statement).first()
  371. return result is not None
  372. finally:
  373. engine.dispose(close=True)
  374. def _parse_well_name(well_name: str) -> tuple[int, int]:
  375. """Parse well name like 'B5' or 'AE19' into (row, column) indices.
  376. Supports both single-letter (A-Z) and multi-letter (AA, AB, ...) row names
  377. for plates with more than 26 rows.
  378. Parameters
  379. ----------
  380. well_name : str
  381. Well name (e.g., 'B5', 'A1', 'AE19')
  382. Returns
  383. -------
  384. tuple[int, int]
  385. (row, column) - Zero-indexed row and column
  386. Raises
  387. ------
  388. ValueError
  389. If well_name is not in the expected format
  390. """
  391. if not well_name or len(well_name) < 2:
  392. raise ValueError(
  393. f"Invalid well name: '{well_name}'. Expected format like 'B5', 'AE19'"
  394. )
  395. # Split into letter prefix and number suffix
  396. i = 0
  397. while i < len(well_name) and well_name[i].isalpha():
  398. i += 1
  399. if i == 0:
  400. raise ValueError(f"Invalid well name: '{well_name}'. Must start with letter(s)")
  401. if i == len(well_name) or not well_name[i:].isdigit():
  402. raise ValueError(
  403. f"Invalid well name: '{well_name}'. Expected format like 'B5', 'AE19' "
  404. f"(letter(s) followed by number)"
  405. )
  406. row_label = well_name[:i]
  407. row = _label_to_row_index(row_label)
  408. col = int(well_name[i:]) - 1
  409. return row, col
  410. def _label_to_row_index(label: str) -> int:
  411. """Convert well row label to zero-indexed row number.
  412. Supports single and multi-letter labels using base-26 alphabet.
  413. A=0, B=1, ..., Z=25, AA=26, AB=27, ..., AZ=51, etc.
  414. Parameters
  415. ----------
  416. label : str
  417. Row label (e.g., 'A', 'Z', 'AA', 'AE')
  418. Returns
  419. -------
  420. int
  421. Zero-indexed row number
  422. Examples
  423. --------
  424. >>> _label_to_row_index("A")
  425. 0
  426. >>> _label_to_row_index("Z")
  427. 25
  428. >>> _label_to_row_index("AA")
  429. 26
  430. >>> _label_to_row_index("AE")
  431. 30
  432. """
  433. label = label.upper()
  434. result = 0
  435. for char in label:
  436. result = result * 26 + (ord(char) - ord("A") + 1)
  437. return result - 1
  438. # OLD WAY TO STORE DATA --------------------------------------------------------------
  439. # Define a type variable for the BaseClass
  440. T = TypeVar("T", bound="BaseClass")
  441. @dataclass
  442. class BaseClass:
  443. """Base class for all classes in the package."""
  444. def replace(self: T, **kwargs: Any) -> T:
  445. """Replace the values of the dataclass with the given keyword arguments."""
  446. return replace(self, **kwargs)
  447. # fmt: off
  448. @dataclass
  449. class ROIData(BaseClass):
  450. """Data container for ROI (Region of Interest) analysis results.
  451. This dataclass stores comprehensive analysis data for a single ROI including
  452. raw fluorescence traces, neuropil correction, calcium dynamics (dff, denoised),
  453. peak detection, inferred spikes, and experimental metadata.
  454. Parameters
  455. ----------
  456. well_fov_position : str
  457. Position identifier (e.g., "B5_0000_p0" for well B5, fov0, position 0)
  458. raw_trace : list[float] | None
  459. Original raw fluorescence trace before any neuropil correction
  460. corrected_trace : list[float] | None
  461. Raw fluorescence trace after neuropil correction (if enabled),
  462. otherwise same as raw_trace. This is used for all
  463. downstream analysis.
  464. neuropil_trace : list[float] | None
  465. Fluorescence trace from the neuropil (donut-shaped region around ROI)
  466. neuropil_correction_factor : float | None
  467. Correction factor used for neuropil subtraction
  468. dff : list[float] | None
  469. ΔF/F (delta F over F) - normalized fluorescence change
  470. den_dff : list[float] | None
  471. Denoised ΔF/F trace (using OASIS algorithm) for calcium event detection
  472. peaks_den_dff : list[float] | None
  473. Indices of detected peaks in the denoised trace
  474. peaks_amplitudes_den_dff : list[float] | None
  475. Amplitude values of detected peaks in denoised trace
  476. peaks_prominence_den_dff : float | None
  477. Prominence threshold used for peak detection
  478. peaks_height_den_dff : float | None
  479. Height threshold used for peak detection
  480. inferred_spikes : list[float] | None
  481. Inferred spike probabilities from deconvolution
  482. inferred_spikes_threshold : float | None
  483. Threshold for spike detection
  484. den_dff_frequency : float | None
  485. Frequency of calcium events in Hz
  486. condition_1 : str | None
  487. First experimental condition (e.g., genotype)
  488. condition_2 : str | None
  489. Second experimental condition (e.g., treatment)
  490. cell_size : float | None
  491. ROI area in µm² or pixels
  492. cell_size_units : str | None
  493. Units for cell_size ("µm" or "pixel")
  494. elapsed_time_list_ms : list[float] | None
  495. Timestamp for each frame in milliseconds
  496. total_recording_time_sec : float | None
  497. Total recording duration in seconds
  498. active : bool | None
  499. Whether the ROI shows calcium activity (has detected peaks)
  500. iei : list[float] | None
  501. Inter-event intervals between calcium peaks (in seconds)
  502. evoked_experiment : bool
  503. Whether this is an optogenetic stimulation experiment
  504. stimulated : bool
  505. Whether this ROI overlaps with the stimulated area
  506. stimulations_frames_and_powers : dict[str, int] | None
  507. Frame numbers and LED powers for stimulation events
  508. led_pulse_duration : str | None
  509. Duration of LED pulse in stimulation experiments
  510. led_power_equation : str | None
  511. Equation to calculate LED power density (mW/cm²)
  512. calcium_sync_jitter_window : int | None
  513. Jitter window (frames) for calcium peak synchrony analysis
  514. spikes_sync_cross_corr_lag : int | None
  515. Maximum lag (frames) for spike cross-correlation synchrony
  516. calcium_network_threshold : float | None
  517. Percentile threshold (0-100) for network connectivity
  518. spikes_burst_threshold : float | None
  519. Threshold (%) for burst detection in spike trains
  520. spikes_burst_min_duration : int | None
  521. Minimum burst duration in seconds
  522. spikes_burst_gaussian_sigma : float | None
  523. Sigma for Gaussian smoothing in burst detection (seconds)
  524. mask_coord_and_shape : tuple[tuple[list[int], list[int]], tuple[int, int]] | None
  525. ROI mask stored as ((y_coords, x_coords), (height, width))
  526. neuropil_mask_coord_and_shape : tuple | None
  527. Neuropil mask: ((y_coords, x_coords), (height, width))
  528. """
  529. well_fov_position: str = ""
  530. raw_trace: list[float] | None = None
  531. corrected_trace: list[float] | None = None
  532. neuropil_trace: list[float] | None = None
  533. neuropil_correction_factor: float | None = None
  534. dff: list[float] | None = None
  535. den_dff: list[float] | None = None # denoised dff with oasis package
  536. peaks_den_dff: list[float] | None = None
  537. peaks_amplitudes_den_dff: list[float] | None = None
  538. peaks_prominence_den_dff: float | None = None
  539. peaks_height_den_dff: float | None = None
  540. inferred_spikes: list[float] | None = None
  541. inferred_spikes_threshold: float | None = None
  542. den_dff_frequency: float | None = None # Hz
  543. condition_1: str | None = None
  544. condition_2: str | None = None
  545. cell_size: float | None = None
  546. cell_size_units: str | None = None
  547. elapsed_time_list_ms: list[float] | None = None # in ms
  548. total_recording_time_sec: float | None = None # in seconds
  549. active: bool | None = None
  550. iei: list[float] | None = None # interevent interval
  551. evoked_experiment: bool = False
  552. stimulated: bool = False
  553. stimulations_frames_and_powers: dict[str, int] | None = None
  554. led_pulse_duration: str | None = None
  555. led_power_equation: str | None = None # equation for LED power
  556. calcium_sync_jitter_window: int | None = None # in frames
  557. spikes_sync_cross_corr_lag: int | None = None # in frames
  558. calcium_network_threshold: float | None = None # percentile (0-100)
  559. spikes_burst_threshold: float | None = None # in percent
  560. spikes_burst_min_duration: int | None = None # in seconds
  561. spikes_burst_gaussian_sigma: float | None = None # in seconds
  562. # store ROI mask as coordinates (y_coords, x_coords) and shape (height, width)
  563. mask_coord_and_shape: tuple[tuple[list[int], list[int]], tuple[int, int]] | None = None # noqa: E501
  564. # store neuropil mask as coordinates (y_coords, x_coords) and shape (height, width)
  565. neuropil_mask_coord_and_shape: tuple[tuple[list[int], list[int]], tuple[int, int]] | None = None # noqa: E501
  566. # fmt: on

_util.py at commit 336e7b5, under BSD-3-Clause · at the source

Overview

Authors: Wardiya Afshar‐Saber1,2, Federico M Gasparoli3, Ziqin Yang1,2, Nicole A Teaney1,2, Rachel Hobson1,2, Lahin Lalani1,2, Gayathri Srinivasan2,4, Dosh Whye2,4, Ranit Karmakar3, Elizabeth D Buttermore2,4, Kellen D Winden1,2, Cidi Chen2,4, Mustafa Sahin1,2,4
  1. Department of Neurology, F.M. Kirby Neurobiology Center, Harvard Medical School, Boston Children's Hospital, Boston, Massachusetts, USA
  2. Rosamund Stone Zander and Hansjoerg Wyss Translational Neuroscience Center, Boston, Massachusetts, USA
  3. Department of Systems Biology, Harvard Medical School, Boston, Massachusetts, USA
  4. Human Neuron Core, Boston Children's Hospital, Boston, Massachusetts, USA
Institutions: Boston Children's Hospital (United States); Harvard University (United States)
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany), volume 13, issue 26, article e15887
Dates: received 18 August 2025; accepted 24 February 2026; published online 9 March 2026; in print May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1002/advs.202515887 · PMID 41801223 · PMCID PMC13159138 · OpenAlex W7134279140
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: optical imaging (calcium, voltage, 2-photon) (modality), human (organism), other condition (population)
Methods: Connectivity, Evoked potentials, Statistics, fMRI & imaging, Single-unit activity, calcium imaging, Spectral & time-frequency
Keywords: calcium imaging, machine learning, neurodevelopmental disorders, open‐source, optogenetics, stem cells
MeSH: Calcium*, Induced Pluripotent Stem Cells*, Neurons*, Optogenetics*, Humans (* major topic)
Topic: Single-cell and spatial transcriptomics (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: Congressionally Directed Medical Research Programs (W81XWH2110209); Equipment and Core Resources Allocation Committee award at Boston Children's Hospital; Rosamund Stone Zander and Hansjoerg Wyss Translational Neuroscience Center
Citations: cited by 1 paper (Europe PMC); 46 references in the paper
Research resources: RRID:Addgene_120896, the puromycin RRID:Addgene_79049, neomycin RRID:Addgene_99378

Abstract

High‐throughput, single‐cell resolution profiling of neuronal activity is critical for understanding brain function and modeling neurological disorders, yet existing approaches are often limited by scalability and manual workflows. Here, we present an open‐source, scalable imaging and analysis platform that integrates optogenetic stimulation, calcium imaging, automated acquisition, single‐cell and network analyses. The platform enables robust quantification of spontaneous and evoked neuronal activity across hundreds of human stem cell‐derived neurons over multiple timepoints, supporting functional phenotyping at both cellular and network levels. We demonstrate the versatility of the platform across multiple disease‐relevant contexts, including models of CDKL5 Deficiency, SSADH Deficiency, and tuberous sclerosis complex (TSC). Additionally, we generate CRISPR‐Cas9 knock‐in hiPSC lines expressing GCaMP6s and demonstrate partial reversal through pharmacological intervention in TSC. By linking single‐cell dynamics to network‐level measures, this platform provides a generalizable framework for scalable functional phenotyping and high‐throughput screening in human neuronal models.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 19 matches between paragraphs and lines of code.

fdrgsp/micromanager-gui

License: BSD-3-Clause
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: c6b565b09a3042be0c67c95388c59e2d26ad068d, 8 July 2026
Languages: Python (46)
Size: 61 files, 46 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, license file, environment (pyproject.toml), tests, continuous integration
Not found: CITATION.cff, documentation
Tools: NumPy (9 files), tifffile (6 files), Cellpose (1 file), pandas (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
48 files

fdrgsp/cali

License: BSD-3-Clause
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 336e7b59d0df7940ddc93d04b4f691b1303cfc3f, 11 June 2026
Languages: Python (175), Jupyter (4)
Size: 1,629 files, 179 scripts
Software Heritage: not archived
Found in: the text, “Introduction”
Holds: README, license file, environment (pyproject.toml), tests, continuous integration, 4 notebooks
Not found: CITATION.cff, documentation
Tools: NumPy (82 files), tifffile (20 files), pandas (10 files), SciPy (8 files), Matplotlib (5 files), Numba (3 files), scikit-image (3 files), Cellpose (2 files), PyTorch (2 files), scikit-learn (2 files)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
181 files
At the source: github.com/fdrgsp/cali

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 225 scripts, each with its path and the digest of its content;
  • 19 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data Availability Statement

The data that support the findings of this study are openly available in FigShare at (https://doi.org/10.6084/m9.figshare.29599163). All data reported in this paper will be shared by the lead contact by request. All code is available on github (https://github.com/fdrgsp/micromanager‐gui (https://github.com/fdrgsp/micromanager-gui)).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 13 authors, 6 keywords, 5 MeSH terms, 3 funders, 41 references, 3 RRIDs.

Cite

This paper

Afshar‐Saber, W., Gasparoli, F. M., Yang, Z., Teaney, N. A., Hobson, R., Lalani, L., Srinivasan, G., Whye, D., Karmakar, R., Buttermore, E. D., Winden, K. D., Chen, C., & Sahin, M. (2026). An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons. Advanced science (Weinheim, Baden-Wurttemberg, Germany), 13(26), e15887. https://doi.org/10.1002/advs.202515887

BibTeX

@article{afsharsaber2026open,
author = {Afshar‐Saber, Wardiya and Gasparoli, Federico M and Yang, Ziqin and Teaney, Nicole A and Hobson, Rachel and Lalani, Lahin and Srinivasan, Gayathri and Whye, Dosh and Karmakar, Ranit and Buttermore, Elizabeth D and Winden, Kellen D and Chen, Cidi and Sahin, Mustafa},
title = {{An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons}},
journal = {Advanced science (Weinheim, Baden-Wurttemberg, Germany)},
year = {2026},
month = mar,
volume = {13},
number = {26},
pages = {e15887},
publisher = {Wiley},
issn = {2198-3844},
doi = {10.1002/advs.202515887},
url = {https://doi.org/10.1002/advs.202515887},
pmid = {41801223},
pmcid = {PMC13159138}
}

RIS

TY - JOUR
AU - Afshar‐Saber, Wardiya
AU - Gasparoli, Federico M
AU - Yang, Ziqin
AU - Teaney, Nicole A
AU - Hobson, Rachel
AU - Lalani, Lahin
AU - Srinivasan, Gayathri
AU - Whye, Dosh
AU - Karmakar, Ranit
AU - Buttermore, Elizabeth D
AU - Winden, Kellen D
AU - Chen, Cidi
AU - Sahin, Mustafa
TI - An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons
T2 - Advanced science (Weinheim, Baden-Wurttemberg, Germany)
J2 - Adv Sci (Weinh)
PY - 2026
DA - 2026/03/09
VL - 13
IS - 26
SP - e15887
SN - 2198-3844
PB - Wiley
DO - 10.1002/advs.202515887
UR - https://doi.org/10.1002/advs.202515887
LA - en
ER -

CSL-JSON

{
"id": "10.1002/advs.202515887",
"type": "article-journal",
"title": "An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons",
"container-title": "Advanced science (Weinheim, Baden-Wurttemberg, Germany)",
"author": [
{
"family": "Afshar‐Saber",
"given": "Wardiya"
},
{
"family": "Gasparoli",
"given": "Federico M"
},
{
"family": "Yang",
"given": "Ziqin"
},
{
"family": "Teaney",
"given": "Nicole A"
},
{
"family": "Hobson",
"given": "Rachel"
},
{
"family": "Lalani",
"given": "Lahin"
},
{
"family": "Srinivasan",
"given": "Gayathri"
},
{
"family": "Whye",
"given": "Dosh"
},
{
"family": "Karmakar",
"given": "Ranit"
},
{
"family": "Buttermore",
"given": "Elizabeth D"
},
{
"family": "Winden",
"given": "Kellen D"
},
{
"family": "Chen",
"given": "Cidi"
},
{
"family": "Sahin",
"given": "Mustafa"
}
],
"container-title-short": "Adv Sci (Weinh)",
"volume": "13",
"issue": "26",
"page": "e15887",
"DOI": "10.1002/advs.202515887",
"PMID": "41801223",
"PMCID": "PMC13159138",
"ISSN": "2198-3844",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/advs.202515887",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
9
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: Cellpose, tifffile, Numba, 7 other tools, 1 reference
[2] doi:10.1038/s41467-026-71458-0 [code]
Early differential impact of MeCP2 mutations on functional networks in Rett syndrome patient-derived human cortical organoids.
Journal: Nature communications
In common: Cellpose, tifffile, Numba, 6 other tools, other condition
[3] doi:10.1073/pnas.2609132123 [code]
A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: Cellpose, tifffile, scikit-image, 5 other tools, 1 reference
[4] doi:10.1038/s41592-026-03065-2 [code]
PinkyCaMP: an mScarlet-based calcium sensor with enhanced brightness, photostability and multiplexing capabilities.
Journal: Nature methods
In common: pandas, SciPy, Matplotlib, 1 other tool, optical imaging (calcium, voltage, 2-photon), 5 references
[5] doi:10.1126/sciadv.aeb4205 [code]
Learning stochastic dynamics and cell-fate landscapes from single-cell snapshots via optimal transport.
Journal: Science advances
In common: Cellpose, tifffile, scikit-image, 6 other tools
[6] doi:10.1016/j.isci.2026.116206 [code]
Gut distension evokes rapid neural dynamics in vagal and hindbrain populations of larval zebrafish.
Journal: iScience
In common: tifffile, scikit-image, PyTorch, 5 other tools, optical imaging (calcium, voltage, 2-photon), 1 reference
[7] doi:10.1038/s41598-026-57519-w [code]
Automated segmentation of neurons and spinal cord structures in immunofluorescence images using SpineDL.
Journal: Scientific reports
In common: tifffile, Numba, scikit-image, 6 other tools, other condition
[8] doi:10.1038/s41467-026-71418-8 [code]
Deep visual proteomics uncovers nociceptor diversity and pain targets.
Journal: Nature communications
In common: tifffile, Numba, scikit-image, 5 other tools, 1 reference
[9] doi:10.1038/s42003-026-10063-9 [code]
Conserved Kir channel mechanisms governing intrinsic excitability in human and rodent parvalbumin neurons.
Journal: Communications biology
In common: Cellpose, tifffile, scikit-image, 5 other tools
[10] doi:10.1038/s41586-026-10323-y [code]
Genetically encoded assembly recorder temporally resolves cellular history.
Journal: Nature
In common: Cellpose, tifffile, scikit-image, 5 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.