OSCR

Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

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 · 521 lines · 20 KB · GPL-3.0

  1. import nrrd
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from matplotlib.widgets import Slider, Button, TextBox, RadioButtons
  5. from matplotlib.colors import ListedColormap, Normalize
  6. import json
  7. def load_annotation(filepath):
  8. """Load the NRRD annotation file"""
  9. data, header = nrrd.read(filepath)
  10. return data, header
  11. def load_structure_tree(filepath):
  12. """Load the structure tree JSON file and create ID to name mapping"""
  13. with open(filepath, 'r') as f:
  14. data = json.load(f)
  15. # Create a mapping from structure ID to structure info
  16. id_to_info = {}
  17. def traverse_tree(node):
  18. """Recursively traverse the structure tree"""
  19. structure_id = node.get('id')
  20. name = node.get('name', 'Unknown')
  21. acronym = node.get('acronym', '')
  22. if structure_id is not None:
  23. id_to_info[structure_id] = {
  24. 'name': name,
  25. 'acronym': acronym,
  26. 'color_hex': node.get('color_hex_triplet', 'FFFFFF')
  27. }
  28. # Process children
  29. if 'children' in node:
  30. for child in node['children']:
  31. traverse_tree(child)
  32. # Handle different JSON structures
  33. if isinstance(data, dict):
  34. # Check if there's a 'msg' key (common in Allen Institute files)
  35. if 'msg' in data:
  36. root_nodes = data['msg']
  37. else:
  38. root_nodes = [data]
  39. elif isinstance(data, list):
  40. root_nodes = data
  41. else:
  42. root_nodes = [data]
  43. # Traverse all root nodes
  44. for root in root_nodes:
  45. traverse_tree(root)
  46. return id_to_info
  47. def create_color_mapped_image(slice_data, structure_info):
  48. """Create RGB image from structure IDs using their defined colors"""
  49. # Create RGB image
  50. height, width = slice_data.shape
  51. rgb_image = np.zeros((height, width, 3), dtype=np.uint8)
  52. for i in range(height):
  53. for j in range(width):
  54. struct_id = int(slice_data[i, j])
  55. if struct_id in structure_info and struct_id != 0:
  56. hex_color = structure_info[struct_id]['color_hex']
  57. # Convert hex to RGB
  58. r = int(hex_color[0:2], 16)
  59. g = int(hex_color[2:4], 16)
  60. b = int(hex_color[4:6], 16)
  61. rgb_image[i, j] = [r, g, b]
  62. else:
  63. # Black for background or unknown
  64. rgb_image[i, j] = [0, 0, 0]
  65. return rgb_image
  66. def interactive_viewer_advanced(annotation_data, structure_info):
  67. """
  68. Advanced interactive viewer with:
  69. - Slice navigation (slider + mouse wheel)
  70. - Colorbar
  71. - Adjustable color range (slider + manual input)
  72. - Axis switching
  73. - Rotation
  74. - Zoom and Pan
  75. - Structure name display
  76. - Color mode selection (ID vs Structure colors)
  77. """
  78. # Create figure and axis
  79. fig = plt.figure(figsize=(16, 10))
  80. # Main image axis
  81. ax_img = plt.axes([0.1, 0.30, 0.62, 0.60])
  82. # Colorbar axis
  83. ax_cbar = plt.axes([0.74, 0.30, 0.02, 0.60])
  84. # Info panel axis (for displaying structure details)
  85. ax_info = plt.axes([0.78, 0.65, 0.20, 0.25])
  86. ax_info.axis('off')
  87. # Radio buttons for color mode
  88. ax_radio = plt.axes([0.78, 0.92, 0.15, 0.07])
  89. # Slider axes
  90. ax_slice = plt.axes([0.1, 0.20, 0.62, 0.03])
  91. ax_vmin = plt.axes([0.1, 0.15, 0.62, 0.03])
  92. ax_vmax = plt.axes([0.1, 0.10, 0.62, 0.03])
  93. # Text box axes for manual input
  94. ax_text_vmin = plt.axes([0.1, 0.05, 0.08, 0.03])
  95. ax_text_vmax = plt.axes([0.22, 0.05, 0.08, 0.03])
  96. # Button axes for switching views
  97. ax_btn_coronal = plt.axes([0.78, 0.57, 0.08, 0.04])
  98. ax_btn_sagittal = plt.axes([0.87, 0.57, 0.08, 0.04])
  99. ax_btn_axial = plt.axes([0.78, 0.52, 0.08, 0.04])
  100. # Button axes for rotation
  101. ax_btn_rot_ccw = plt.axes([0.78, 0.45, 0.08, 0.04])
  102. ax_btn_rot_cw = plt.axes([0.87, 0.45, 0.08, 0.04])
  103. # Button axes for zoom
  104. ax_btn_zoom_in = plt.axes([0.78, 0.38, 0.08, 0.04])
  105. ax_btn_zoom_out = plt.axes([0.87, 0.38, 0.08, 0.04])
  106. # Other buttons
  107. ax_btn_reset = plt.axes([0.78, 0.31, 0.08, 0.04])
  108. ax_btn_apply = plt.axes([0.34, 0.05, 0.06, 0.03])
  109. ax_btn_reset_view = plt.axes([0.87, 0.31, 0.08, 0.04])
  110. # Initialize parameters
  111. current_axis = [0] # 0=coronal, 1=sagittal, 2=axial
  112. rotation_angle = [0] # Current rotation angle in degrees
  113. zoom_level = [1.0] # Current zoom level
  114. current_slice_idx = [annotation_data.shape[0] // 2] # Store current slice index
  115. color_mode = ['by_id'] # 'by_id' or 'by_structure'
  116. # Get initial min/max values
  117. data_min = float(np.min(annotation_data))
  118. data_max = float(np.max(annotation_data))
  119. axis_names = ['Coronal', 'Sagittal', 'Axial']
  120. # Create function to get slice data
  121. def get_slice_data(slice_idx=None, axis=None, rotation=None):
  122. """Get current slice data based on axis and rotation"""
  123. if slice_idx is None:
  124. slice_idx = current_slice_idx[0]
  125. if axis is None:
  126. axis = current_axis[0]
  127. if rotation is None:
  128. rotation = rotation_angle[0]
  129. slice_idx = int(slice_idx)
  130. if axis == 0:
  131. slice_data = annotation_data[slice_idx, :, :].T
  132. elif axis == 1:
  133. slice_data = annotation_data[:, slice_idx, :].T
  134. else:
  135. slice_data = annotation_data[:, :, slice_idx].T
  136. # Apply rotation
  137. k = rotation // 90 # Number of 90-degree rotations
  138. if k != 0:
  139. slice_data = np.rot90(slice_data, k)
  140. return slice_data
  141. # Create initial image
  142. initial_slice = get_slice_data()
  143. # Display image (initially by ID)
  144. img = ax_img.imshow(initial_slice, cmap='nipy_spectral', origin='lower',
  145. vmin=data_min, vmax=data_max)
  146. ax_img.set_title(f'{axis_names[current_axis[0]]} Slice {current_slice_idx[0]} (Rotation: {rotation_angle[0]}°) - Color by ID',
  147. fontsize=14, fontweight='bold')
  148. ax_img.axis('on')
  149. # Add colorbar
  150. cbar = plt.colorbar(img, cax=ax_cbar)
  151. cbar.set_label('Structure ID', rotation=270, labelpad=20, fontsize=12)
  152. # Info panel text
  153. info_text = ax_info.text(0.05, 0.95, 'Hover over image\nfor structure info',
  154. transform=ax_info.transAxes,
  155. fontsize=10, verticalalignment='top',
  156. family='monospace',
  157. bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8))
  158. # Create radio buttons for color mode
  159. radio = RadioButtons(ax_radio, ('Color by ID', 'Color by Structure'))
  160. # Create sliders
  161. slider_slice = Slider(ax_slice, 'Slice', 0, annotation_data.shape[current_axis[0]]-1,
  162. valinit=current_slice_idx[0], valstep=1)
  163. slider_vmin = Slider(ax_vmin, 'Min ID', data_min, data_max,
  164. valinit=data_min, valstep=1)
  165. slider_vmax = Slider(ax_vmax, 'Max ID', data_min, data_max,
  166. valinit=data_max, valstep=1)
  167. # Create text boxes for manual input
  168. text_box_vmin = TextBox(ax_text_vmin, 'Min:', initial=str(int(data_min)))
  169. text_box_vmax = TextBox(ax_text_vmax, 'Max:', initial=str(int(data_max)))
  170. # Create buttons
  171. btn_coronal = Button(ax_btn_coronal, 'Coronal')
  172. btn_sagittal = Button(ax_btn_sagittal, 'Sagittal')
  173. btn_axial = Button(ax_btn_axial, 'Axial')
  174. btn_rot_ccw = Button(ax_btn_rot_ccw, '↺ 90°')
  175. btn_rot_cw = Button(ax_btn_rot_cw, '↻ 90°')
  176. btn_zoom_in = Button(ax_btn_zoom_in, 'Zoom +')
  177. btn_zoom_out = Button(ax_btn_zoom_out, 'Zoom -')
  178. btn_reset = Button(ax_btn_reset, 'Reset Range')
  179. btn_apply = Button(ax_btn_apply, 'Apply')
  180. btn_reset_view = Button(ax_btn_reset_view, 'Reset View')
  181. # Text for displaying current structure ID under cursor
  182. text_info = ax_img.text(0.02, 0.98, '', transform=ax_img.transAxes,
  183. fontsize=9, verticalalignment='top',
  184. bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.9))
  185. def update_image():
  186. """Update the displayed image"""
  187. current_slice_idx[0] = int(slider_slice.val)
  188. slice_data = get_slice_data()
  189. if color_mode[0] == 'by_structure':
  190. # Create RGB image using structure colors
  191. rgb_image = create_color_mapped_image(slice_data, structure_info)
  192. img.set_data(rgb_image)
  193. img.set_cmap(None) # Remove colormap for RGB
  194. img.set_clim(None, None)
  195. # Hide colorbar for structure color mode
  196. cbar.ax.set_visible(False)
  197. title_suffix = "Color by Structure"
  198. # Disable range sliders
  199. slider_vmin.ax.set_visible(False)
  200. slider_vmax.ax.set_visible(False)
  201. text_box_vmin.ax.set_visible(False)
  202. text_box_vmax.ax.set_visible(False)
  203. btn_apply.ax.set_visible(False)
  204. btn_reset.ax.set_visible(False)
  205. else:
  206. # Use ID-based coloring
  207. img.set_data(slice_data)
  208. img.set_cmap('nipy_spectral')
  209. vmin = slider_vmin.val
  210. vmax = slider_vmax.val
  211. img.set_clim(vmin, vmax)
  212. # Show colorbar for ID mode
  213. cbar.ax.set_visible(True)
  214. title_suffix = "Color by ID"
  215. # Enable range sliders
  216. slider_vmin.ax.set_visible(True)
  217. slider_vmax.ax.set_visible(True)
  218. text_box_vmin.ax.set_visible(True)
  219. text_box_vmax.ax.set_visible(True)
  220. btn_apply.ax.set_visible(True)
  221. btn_reset.ax.set_visible(True)
  222. img.set_extent([0, slice_data.shape[1], 0, slice_data.shape[0]])
  223. ax_img.set_title(f'{axis_names[current_axis[0]]} Slice {current_slice_idx[0]} (Rotation: {rotation_angle[0]}°) - {title_suffix}',
  224. fontsize=14, fontweight='bold')
  225. fig.canvas.draw_idle()
  226. def update_slice(val):
  227. """Update the displayed slice"""
  228. update_image()
  229. def on_scroll(event):
  230. """Handle mouse wheel scroll to navigate slices"""
  231. if event.inaxes == ax_img:
  232. # Get current slice and max slice
  233. current_slice = int(slider_slice.val)
  234. max_slice = annotation_data.shape[current_axis[0]] - 1
  235. # Scroll up = next slice, scroll down = previous slice
  236. if event.button == 'up':
  237. new_slice = min(current_slice + 1, max_slice)
  238. elif event.button == 'down':
  239. new_slice = max(current_slice - 1, 0)
  240. else:
  241. return
  242. # Update slider (this will trigger update_image through the slider callback)
  243. slider_slice.set_val(new_slice)
  244. def update_vmin(val):
  245. """Update minimum color value"""
  246. if color_mode[0] == 'by_id':
  247. vmin = slider_vmin.val
  248. vmax = slider_vmax.val
  249. if vmin < vmax:
  250. img.set_clim(vmin=vmin)
  251. text_box_vmin.set_val(str(int(vmin)))
  252. fig.canvas.draw_idle()
  253. else:
  254. slider_vmin.set_val(vmax - 1)
  255. def update_vmax(val):
  256. """Update maximum color value"""
  257. if color_mode[0] == 'by_id':
  258. vmin = slider_vmin.val
  259. vmax = slider_vmax.val
  260. if vmax > vmin:
  261. img.set_clim(vmax=vmax)
  262. text_box_vmax.set_val(str(int(vmax)))
  263. fig.canvas.draw_idle()
  264. else:
  265. slider_vmax.set_val(vmin + 1)
  266. def apply_manual_range(event):
  267. """Apply manually entered min/max values"""
  268. if color_mode[0] == 'by_id':
  269. try:
  270. vmin = float(text_box_vmin.text)
  271. vmax = float(text_box_vmax.text)
  272. if vmin >= vmax:
  273. print("Error: Min ID must be less than Max ID")
  274. return
  275. if vmin < data_min or vmax > data_max:
  276. print(f"Warning: Values outside data range [{data_min}, {data_max}]")
  277. slider_vmin.set_val(vmin)
  278. slider_vmax.set_val(vmax)
  279. img.set_clim(vmin=vmin, vmax=vmax)
  280. fig.canvas.draw_idle()
  281. print(f"Applied range: [{vmin}, {vmax}]")
  282. except ValueError:
  283. print("Error: Please enter valid numbers")
  284. def submit_vmin(text):
  285. """Handle text box submit for vmin"""
  286. apply_manual_range(None)
  287. def submit_vmax(text):
  288. """Handle text box submit for vmax"""
  289. apply_manual_range(None)
  290. def change_color_mode(label):
  291. """Change between ID and structure color modes"""
  292. if label == 'Color by ID':
  293. color_mode[0] = 'by_id'
  294. else:
  295. color_mode[0] = 'by_structure'
  296. update_image()
  297. def rotate_ccw(event):
  298. """Rotate counter-clockwise by 90 degrees"""
  299. rotation_angle[0] = (rotation_angle[0] - 90) % 360
  300. update_image()
  301. def rotate_cw(event):
  302. """Rotate clockwise by 90 degrees"""
  303. rotation_angle[0] = (rotation_angle[0] + 90) % 360
  304. update_image()
  305. def zoom_in(event):
  306. """Zoom in by reducing view limits"""
  307. zoom_level[0] *= 1.3
  308. xlim = ax_img.get_xlim()
  309. ylim = ax_img.get_ylim()
  310. x_center = (xlim[0] + xlim[1]) / 2
  311. y_center = (ylim[0] + ylim[1]) / 2
  312. x_range = (xlim[1] - xlim[0]) / 1.3
  313. y_range = (ylim[1] - ylim[0]) / 1.3
  314. ax_img.set_xlim(x_center - x_range/2, x_center + x_range/2)
  315. ax_img.set_ylim(y_center - y_range/2, y_center + y_range/2)
  316. fig.canvas.draw_idle()
  317. def zoom_out(event):
  318. """Zoom out by expanding view limits"""
  319. zoom_level[0] /= 1.3
  320. xlim = ax_img.get_xlim()
  321. ylim = ax_img.get_ylim()
  322. x_center = (xlim[0] + xlim[1]) / 2
  323. y_center = (ylim[0] + ylim[1]) / 2
  324. x_range = (xlim[1] - xlim[0]) * 1.3
  325. y_range = (ylim[1] - ylim[0]) * 1.3
  326. # Don't zoom out beyond initial limits
  327. slice_data = get_slice_data()
  328. max_x = slice_data.shape[1]
  329. max_y = slice_data.shape[0]
  330. new_xlim = [max(0, x_center - x_range/2), min(max_x, x_center + x_range/2)]
  331. new_ylim = [max(0, y_center - y_range/2), min(max_y, y_center + y_range/2)]
  332. ax_img.set_xlim(new_xlim)
  333. ax_img.set_ylim(new_ylim)
  334. fig.canvas.draw_idle()
  335. def reset_view(event):
  336. """Reset zoom and pan to original view"""
  337. zoom_level[0] = 1.0
  338. slice_data = get_slice_data()
  339. ax_img.set_xlim(0, slice_data.shape[1])
  340. ax_img.set_ylim(0, slice_data.shape[0])
  341. fig.canvas.draw_idle()
  342. def switch_axis(new_axis):
  343. """Switch viewing axis"""
  344. current_axis[0] = new_axis
  345. rotation_angle[0] = 0 # Reset rotation when switching axis
  346. # Update slice slider range and value
  347. current_slice_idx[0] = annotation_data.shape[new_axis] // 2
  348. slider_slice.valmax = annotation_data.shape[new_axis] - 1
  349. slider_slice.ax.set_xlim(0, annotation_data.shape[new_axis] - 1)
  350. slider_slice.set_val(current_slice_idx[0])
  351. # Reset view
  352. reset_view(None)
  353. def reset_range(event):
  354. """Reset color range to full data range"""
  355. slider_vmin.set_val(data_min)
  356. slider_vmax.set_val(data_max)
  357. text_box_vmin.set_val(str(int(data_min)))
  358. text_box_vmax.set_val(str(int(data_max)))
  359. def on_mouse_move(event):
  360. """Display structure ID and name under cursor"""
  361. if event.inaxes == ax_img and event.xdata is not None and event.ydata is not None:
  362. # Get current slice data to determine dimensions
  363. slice_data = get_slice_data()
  364. x, y = int(event.xdata), int(event.ydata)
  365. if 0 <= x < slice_data.shape[1] and 0 <= y < slice_data.shape[0]:
  366. structure_id = int(slice_data[y, x])
  367. # Get structure information
  368. if structure_id in structure_info:
  369. info = structure_info[structure_id]
  370. name = info['name']
  371. acronym = info['acronym']
  372. # Update small overlay text
  373. text_info.set_text(f'Pos: ({x}, {y})\nID: {structure_id}\n{acronym}')
  374. # Update info panel with word wrapping for long names
  375. info_display = f"Structure ID: {structure_id}\n\n"
  376. info_display += f"Acronym: {acronym}\n\n"
  377. info_display += f"Name:\n{name}\n\n"
  378. info_display += f"Position: ({x}, {y})"
  379. info_text.set_text(info_display)
  380. else:
  381. text_info.set_text(f'Pos: ({x}, {y})\nID: {structure_id}\n(Unknown)')
  382. info_text.set_text(f"Structure ID: {structure_id}\n\nName: Unknown\n\nPosition: ({x}, {y})")
  383. fig.canvas.draw_idle()
  384. # Connect callbacks
  385. slider_slice.on_changed(update_slice)
  386. slider_vmin.on_changed(update_vmin)
  387. slider_vmax.on_changed(update_vmax)
  388. text_box_vmin.on_submit(submit_vmin)
  389. text_box_vmax.on_submit(submit_vmax)
  390. radio.on_clicked(change_color_mode)
  391. btn_coronal.on_clicked(lambda event: switch_axis(0))
  392. btn_sagittal.on_clicked(lambda event: switch_axis(1))
  393. btn_axial.on_clicked(lambda event: switch_axis(2))
  394. btn_rot_ccw.on_clicked(rotate_ccw)
  395. btn_rot_cw.on_clicked(rotate_cw)
  396. btn_zoom_in.on_clicked(zoom_in)
  397. btn_zoom_out.on_clicked(zoom_out)
  398. btn_reset.on_clicked(reset_range)
  399. btn_apply.on_clicked(apply_manual_range)
  400. btn_reset_view.on_clicked(reset_view)
  401. fig.canvas.mpl_connect('motion_notify_event', on_mouse_move)
  402. fig.canvas.mpl_connect('scroll_event', on_scroll) # Add scroll event
  403. plt.show()
  404. # Main execution
  405. if __name__ == "__main__":
  406. # Define file paths
  407. annotation_filepath = r"C:\DATA\MFA\uCT\uCT2CCF\data\ccf\annotation_25.nrrd"
  408. structure_tree_filepath = r"C:\DATA\MFA\uCT\uCT2CCF\data\ccf\structure_tree.json"
  409. print("Loading annotation file...")
  410. annotation_data, header = load_annotation(annotation_filepath)
  411. print(f"Annotation shape: {annotation_data.shape}")
  412. print(f"Unique structure IDs: {len(np.unique(annotation_data))}")
  413. print(f"Data type: {annotation_data.dtype}")
  414. print(f"Min ID: {np.min(annotation_data)}, Max ID: {np.max(annotation_data)}")
  415. print("\nLoading structure tree...")
  416. structure_info = load_structure_tree(structure_tree_filepath)
  417. print(f"Loaded {len(structure_info)} brain structures")
  418. # Print first few structures to verify loading
  419. if len(structure_info) > 0:
  420. print("\nSample structures:")
  421. for i, (struct_id, info) in enumerate(list(structure_info.items())[:5]):
  422. print(f" ID {struct_id}: {info['acronym']} - {info['name']} (Color: #{info['color_hex']})")
  423. print("\nLaunching interactive viewer...")
  424. print("- Use radio buttons to switch between 'Color by ID' and 'Color by Structure'")
  425. print("- Use 'Slice' slider to navigate through slices")
  426. print("- Scroll mouse wheel over image to move through slices (up=next, down=previous)")
  427. print("- In 'Color by ID' mode: Use sliders/text boxes to adjust color range")
  428. print("- In 'Color by Structure' mode: Each region shows its anatomical color")
  429. print("- Click '↺ 90°' or '↻ 90°' to rotate the view")
  430. print("- Click 'Zoom +' or 'Zoom -' to zoom in/out")
  431. print("- Click 'Reset View' to reset zoom and rotation")
  432. print("- Hover mouse over image to see structure name and ID")
  433. interactive_viewer_advanced(annotation_data, structure_info)

annotation_loader.py at commit e73b4cc, under GPL-3.0 · at the source

Overview

Authors: Manil Bradai1, Mirna Merkler1, Gabriela Gil1, Rebecca Davie1, Shuzo Sakata1
  1. Strathclyde Institute of Pharmacy and Biomedical Sciences University of Strathclyde Glasgow UK
Institutions: University of Strathclyde (United Kingdom)
Journal: The European journal of neuroscience, volume 63, issue 12, article e70582
Dates: received 23 December 2025; accepted 4 June 2026; published online 17 June 2026; in print June 2026
Type: Other · Language: English
License: CC BY
Identifiers: DOI 10.1111/ejn.70582 · PMID 42306926 · PMCID PMC13273707 · OpenAlex W7165010047
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: optical imaging (calcium, voltage, 2-photon) (modality), mouse (organism), systems (subfield)
Methods: Spectral & time-frequency, Statistics, Connectivity, fMRI & imaging, Single-unit activity, calcium imaging, Physiology & signal measures, Machine learning
Keywords: brain state, cell type, fiber photometry, GCaMP, mesoscopic functional mapping
MeSH: Brain*, Brain Mapping*, GABAergic Neurons*, Photometry*, Animals, Male, Mice, Mice, Inbred C57BL, Optical Fibers (* major topic)
Journal subjects: Technical Note
Topic: Photoreceptor and optogenetics research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Funding: European Union’s Horizon 2020 (101016787); Medical Research Council (MR/Y004051/1)
Citations: not cited yet (Europe PMC); 64 references in the paper

Abstract

Mesoscopic functional brain mapping is essential for a better understanding of various brain functions and dysfunctions. However, accessing distributed neural circuits in mammalian brain regions remains a significant challenge. While fiber photometry is a versatile optical approach, existing methods often suffer from invasiveness and limited scalability. Here we present an affordable multifiber array (MFA)‐based photometry system to monitor neural signals across multiple regions. Our system comprises a custom‐designed MFA utilizing 50‐μm diameter optical fibers and off‐the‐shelf optical components. To demonstrate the system's versatility, we monitored GABAergic population activity using jGCaMP8s across multiple brain regions in head‐fixed, awake mice. By combining this approach with pupillometry, we identified state‐dependent, region‐specific GABAergic dynamics. Our MFA‐based photometry system opens new avenues for investigating state‐dependent neural dynamics at the mesoscopic level. To facilitate wider adoption, all code and resources are publicly available on GitHub (https://github.com/Sakata‐Lab/MFA).

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

Repositories

Its files are read in the Code ↔ Paper reader above.

Sakata-Lab/mfaCTpy

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: e73b4cc0b7151b381d0d81fa2411a12c4b336743, 25 June 2026
Languages: Python (10)
Size: 12 files, 10 scripts
Software Heritage: not archived
Found in: the text, “μCT Image Analysis”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (10 files), Matplotlib (9 files), tifffile (9 files), SimpleITK (4 files), SciPy (3 files), OpenCV (1 file), pandas (1 file), Pillow (1 file), pydicom (1 file), scikit-image (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
12 files

Sakata-Lab/MFA

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 48f7ec65ead581f733ee3c718848428eed6bc7f2, 25 June 2026
Languages: MATLAB (1)
Size: 11 files, 1 script
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
3 files

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;
  • 11 scripts, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • 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

No dataset and no data link were found in the paper.

Data Availability Statement

Code and resources are available on GitHub (https://github.com/Sakata‐Lab/MFA (https://github.com/Sakata-Lab/MFA)). Data is available online at https://doi.org/10.15129/9f8ce34b‐f058‐4c4e‐b10b‐efd96fe1b047 (https://doi.org/10.15129/9f8ce34b-f058-4c4e-b10b-efd96fe1b047). All other data underlying this article will be shared on reasonable request to the corresponding author.

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 2, 28 September 2026

  • Publisher: — → Wiley

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 5 keywords, 9 MeSH terms, 2 funders, 64 references.

Cite

This paper

Bradai, M., Merkler, M., Gil, G., Davie, R., & Sakata, S. (2026). Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain. The European journal of neuroscience, 63(12), e70582. https://doi.org/10.1111/ejn.70582

BibTeX

@article{bradai2026multifiber,
author = {Bradai, Manil and Merkler, Mirna and Gil, Gabriela and Davie, Rebecca and Sakata, Shuzo},
title = {{Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain}},
journal = {The European journal of neuroscience},
year = {2026},
month = jun,
volume = {63},
number = {12},
pages = {e70582},
publisher = {Wiley},
issn = {0953-816X},
doi = {10.1111/ejn.70582},
url = {https://doi.org/10.1111/ejn.70582},
pmid = {42306926},
pmcid = {PMC13273707}
}

RIS

TY - JOUR
AU - Bradai, Manil
AU - Merkler, Mirna
AU - Gil, Gabriela
AU - Davie, Rebecca
AU - Sakata, Shuzo
TI - Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain
T2 - The European journal of neuroscience
J2 - Eur J Neurosci
PY - 2026
DA - 2026/06/01
VL - 63
IS - 12
SP - e70582
SN - 0953-816X
PB - Wiley
DO - 10.1111/ejn.70582
UR - https://doi.org/10.1111/ejn.70582
LA - en
ER -

CSL-JSON

{
"id": "10.1111/ejn.70582",
"type": "article-journal",
"title": "Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain",
"container-title": "The European journal of neuroscience",
"author": [
{
"family": "Bradai",
"given": "Manil"
},
{
"family": "Merkler",
"given": "Mirna"
},
{
"family": "Gil",
"given": "Gabriela"
},
{
"family": "Davie",
"given": "Rebecca"
},
{
"family": "Sakata",
"given": "Shuzo"
}
],
"container-title-short": "Eur J Neurosci",
"volume": "63",
"issue": "12",
"page": "e70582",
"DOI": "10.1111/ejn.70582",
"PMID": "42306926",
"PMCID": "PMC13273707",
"ISSN": "0953-816X",
"publisher": "Wiley",
"URL": "https://doi.org/10.1111/ejn.70582",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
1
]
]
}
}

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.1016/j.isci.2026.116206 [code]
Gut distension evokes rapid neural dynamics in vagal and hindbrain populations of larval zebrafish.
Journal: iScience
In common: SimpleITK, tifffile, OpenCV, 6 other tools, optical imaging (calcium, voltage, 2-photon), systems, 2 references
[2] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: tifffile, OpenCV, scikit-image, 5 other tools, systems, mouse, 3 references
[3] doi:10.1038/s41467-026-72710-3 [code]
A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior.
Journal: Nature communications
In common: tifffile, OpenCV, scikit-image, 5 other tools, optical imaging (calcium, voltage, 2-photon), 3 references
[4] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: OpenCV, scikit-image, Pillow, 4 other tools, systems, mouse, 4 references
[5] doi:10.1364/boe.605322 [code]
Generalized plaque digitization framework for multi-dimensional mesoscopic images.
Journal: Biomedical optics express
In common: SimpleITK, tifffile, OpenCV, 6 other tools, mouse, 1 reference
[6] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: tifffile, OpenCV, scikit-image, 5 other tools, optical imaging (calcium, voltage, 2-photon), mouse, 2 references
[7] doi:10.21203/rs.3.rs-10218667/v1 [code]
Combining ultra-flexible electrodes with two-photon imaging to illuminate brain-wide neural dynamics
Journal: Research Square (preprint)
In common: SciPy, Matplotlib, NumPy, optical imaging (calcium, voltage, 2-photon), systems, 6 references
[8] doi:10.3389/fnins.2026.1893619 [code]
Direct laser carbonization of parylene-C toward microelectrodes for &lt;i&gt;in vivo&lt;/i&gt; action potential detection.
Journal: Frontiers in neuroscience
In common: scikit-image, Pillow, pandas, 3 other tools, 5 references
[9] doi:10.1016/j.crmeth.2026.101481 [code]
A hybrid micro-ECoG for functionally targeted multi-site and multi-scale investigation.
Journal: Cell reports methods
In common: systems, 8 references
[10] doi:10.7554/elife.110277 [code]
Functional imaging of nine distinct neuronal populations under a miniscope in freely behaving animals.
Journal: eLife
In common: SimpleITK, tifffile, OpenCV, 4 other tools, optical imaging (calcium, voltage, 2-photon), systems, mouse, 1 reference

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.