OSCR

Visualization and simulation of full-scale point-neuron circuits via the Neural Circuit Visualizer web platform.

Code ↔ Paper

6 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 6 matches
  1. [1] § Methods › CA3-CA1 Pyramidal cell connectome model ↔ api/views.py, lines 1750 to 1859 · score 0.90 · spine density, CA1 pyramidal cell, hippocampal CA3, CA1PC, presynaptic, divergence · Report a mismatch
  2. [2] § Results › Mouse hippocampus ↔ static/js/main.js, lines 4408 to 4467 · score 0.81 · DG axo axonic, CA3 bistratified, CA3 pyramidal, CA1 Pyramidal, granule, mossy · Report a mismatch
  3. [3] § Results › Model parameter setting and job submission ↔ hpc_integration/forms.py, lines 24 to 34 · score 0.78 · inh2inh, inh2pyr, pyr2inh, pyr2pyr, weights, mouse · Report a mismatch
  4. [4] § Methods › CA3-CA1 Pyramidal cell connectome model ↔ api/views.py, lines 1750 to 1859 · score 0.67 · CA3 cell targets, CA1 Cell, fan, Pyramidal, connectivity · Report a mismatch
  5. [5] § Results › Mouse hippocampus ↔ static/js/main.js, lines 4408 to 4467 · score 0.60 · axo axonic, outer, basket, Moleculare, granule, mossy · Report a mismatch
  6. [6] § Methods › Mouse CA1 model ↔ api/views.py, lines 1919 to 1948 · score 0.54 · dorsal ventral, distal, proximal, pyramidal, CA3, connectivity · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 2,329 lines · 87 KB · no license · 3 matches

  1. import base64
  2. from django.http import HttpResponseBadRequest, JsonResponse
  3. from django.views.decorators.csrf import csrf_exempt
  4. import numpy as np
  5. import json, csv
  6. from django.http import JsonResponse
  7. from django.conf import settings
  8. import ujson
  9. import pandas as pd
  10. import os
  11. import array
  12. import requests
  13. from time import sleep
  14. import time
  15. from django.core.paginator import Paginator
  16. from functools import lru_cache
  17. import h5py
  18. import matplotlib
  19. matplotlib.use('Agg') # Use non-GUI backend for Matplotlib
  20. import matplotlib.pyplot as plt
  21. import matplotlib.ticker as ticker
  22. import io
  23. import networkx as nx
  24. from collections import defaultdict
  25. import shutil, re
  26. PART_RE = re.compile(r"\.part(\d+)$")
  27. def _existing_indices(base_dir, fname):
  28. # return {indices_found}, e.g. {0,1,2}
  29. have = set()
  30. prefix = os.path.join(base_dir, f"{fname}.part")
  31. for entry in os.scandir(base_dir):
  32. if not entry.is_file():
  33. continue
  34. name = entry.path
  35. if not name.startswith(prefix):
  36. continue
  37. m = PART_RE.search(name)
  38. if m:
  39. have.add(int(m.group(1)))
  40. return have
  41. def _assemble_if_complete(base_dir, fname, total, filetype):
  42. """Try to assemble if either 0-based or 1-based set is complete.
  43. Returns (assembled_bool, response_dict_or_None)."""
  44. # detect which scheme we’re on using the current file list
  45. have = _existing_indices(base_dir, fname)
  46. # candidate schemes
  47. zero = set(range(total)) # 0..N-1
  48. one = set(range(1, total + 1)) # 1..N
  49. # figure out missing for both
  50. miss_zero = sorted(list(zero - have))
  51. miss_one = sorted(list(one - have))
  52. # Prefer the scheme with fewer missing (ties prefer zero-based)
  53. use_zero = (len(miss_zero) <= len(miss_one))
  54. scheme_missing = miss_zero if use_zero else miss_one
  55. if scheme_missing:
  56. # Not complete yet
  57. missing_paths = [
  58. os.path.join(base_dir, f"{fname}.part{i}") for i in scheme_missing
  59. ]
  60. return False, {'status': 'part-received', 'missing': missing_paths[:50]}
  61. # --- COMPLETE → assemble atomically ---
  62. parts = [os.path.join(base_dir, f"{fname}.part{i}") for i in (zero if use_zero else one)]
  63. final_path = os.path.join(base_dir, fname)
  64. assembling = final_path + ".assembling"
  65. with open(assembling, 'wb') as out_f:
  66. for p in parts:
  67. with open(p, 'rb') as in_f:
  68. shutil.copyfileobj(in_f, out_f, length=1024 * 1024)
  69. out_f.flush()
  70. os.fsync(out_f.fileno())
  71. os.replace(assembling, final_path)
  72. # cleanup
  73. for p in parts:
  74. try: os.remove(p)
  75. except FileNotFoundError: pass
  76. # post-process
  77. if filetype == 'csv':
  78. file_type = detect_file_type(final_path)
  79. if file_type == 'network':
  80. process_network_csv(final_path)
  81. elif file_type == 'activity':
  82. os.replace(final_path, os.path.join(base_dir, 'activity.csv'))
  83. elif filetype in ('hdf5', 'h5'):
  84. process_hdf5_file(final_path)
  85. return True, {'status': 'success', 'path': final_path}
  86. @csrf_exempt
  87. def upload_and_save_Network_file(request):
  88. try:
  89. if request.method != 'POST' or 'file' not in request.FILES:
  90. return JsonResponse({'error': 'POST with file required'}, status=400)
  91. uploaded_file = request.FILES['file']
  92. chunk_index = request.POST.get('chunkIndex')
  93. total_chunks = request.POST.get('totalChunks')
  94. unique_dir = request.POST.get('uniqueDirectory')
  95. ext = (request.POST.get('fileType') or '').lower()
  96. if not all([chunk_index, total_chunks, unique_dir, ext]):
  97. return JsonResponse({'error': 'Missing parameters'}, status=400)
  98. try:
  99. chunk_index = int(chunk_index)
  100. total_chunks = int(total_chunks)
  101. if total_chunks <= 0: raise ValueError
  102. except ValueError:
  103. return JsonResponse({'error': 'Invalid chunkIndex/totalChunks'}, status=400)
  104. base_dir = os.path.join(settings.MEDIA_ROOT, 'csv', unique_dir)
  105. os.makedirs(base_dir, exist_ok=True)
  106. # Save this chunk atomically
  107. part_path = os.path.join(base_dir, f"{uploaded_file.name}.part{chunk_index}")
  108. tmp_path = part_path + ".tmp"
  109. with open(tmp_path, 'wb') as f:
  110. for ch in uploaded_file.chunks():
  111. f.write(ch)
  112. f.flush()
  113. os.fsync(f.fileno())
  114. os.replace(tmp_path, part_path)
  115. # Try assemble immediately (don’t wait for "last")
  116. assembled, payload = _assemble_if_complete(base_dir, uploaded_file.name, total_chunks, ext)
  117. status_code = 200 if assembled or payload.get('status') == 'part-received' else 409
  118. return JsonResponse(payload, status=status_code)
  119. except Exception as e:
  120. return JsonResponse({'error': str(e)}, status=500)
  121. def detect_file_type(file_path):
  122. """ Detects if the file is an activity (2 columns) or network (4 columns) file. """
  123. with open(file_path, 'r') as csv_file:
  124. reader = csv.reader(csv_file)
  125. for row in reader:
  126. if len(row) == 2:
  127. return 'activity'
  128. elif len(row) == 4 or len(row) == 5:
  129. return 'network'
  130. return 'unknown'
  131. def process_network_csv(file_path):
  132. json_directory = os.path.dirname(file_path)
  133. json_path = os.path.join(json_directory, f"network.json")
  134. # Read the CSV and write to JSON
  135. with open(file_path, 'r') as csv_file, open(json_path, 'w') as json_file:
  136. json_data = []
  137. csv_reader = csv.reader(csv_file) # Use csv.reader to avoid treating the first row as a header
  138. first_row = True
  139. for row in csv_reader:
  140. try:
  141. # ---- NEW: skip header if first row is non-numeric ----
  142. if first_row:
  143. first_row = False
  144. try:
  145. int(row[0])
  146. float(row[1])
  147. float(row[2])
  148. float(row[3])
  149. except ValueError:
  150. print("Header detected → skipping first row")
  151. continue
  152. # -----------------------------------------------------
  153. # Ensure the row has at least 4 values
  154. if len(row) == 5:
  155. json_data.append({
  156. 'id': int(row[0]),
  157. 'x': float(row[1]),
  158. 'y': float(row[2]),
  159. 'z': float(row[3]),
  160. # Uncomment if the `type` column is present in the data
  161. 'cell_type': int(row[4]),
  162. })
  163. elif len(row) == 4:
  164. json_data.append({
  165. 'id': int(row[0]),
  166. 'x': float(row[1]),
  167. 'y': float(row[2]),
  168. 'z': float(row[3]),
  169. })
  170. except (ValueError, IndexError) as e:
  171. print(f"Error processing row: {row} - {e}")
  172. # Write all data to JSON in a single valid structure
  173. with open(json_path, 'w') as json_file:
  174. json.dump(json_data, json_file)
  175. # json.dump(json_data, json_file, indent=4) ## for valid json structure use of indent
  176. # Delete the original CSV file after processing
  177. try:
  178. os.remove(file_path)
  179. except OSError as e:
  180. print(f"Error deleting file {file_path}: {e}")
  181. def process_hdf5_file(file_path):
  182. json_directory = os.path.dirname(file_path)
  183. network_json_path = os.path.join(json_directory, "network.json")
  184. activity_csv_path = os.path.join(json_directory, "activity.csv")
  185. with h5py.File(file_path, 'r') as hdf_file:
  186. # Process the 'network' dataset
  187. if 'network' in hdf_file:
  188. network_data = hdf_file['network'][:]
  189. network_json = []
  190. # ---- SAME FUNCTIONALITY AS process_network_csv: handle 4 or 5 columns ----
  191. if network_data.ndim == 2 and network_data.shape[1] == 5:
  192. for record in network_data:
  193. try:
  194. network_json.append({
  195. 'id': int(record[0]),
  196. 'x': float(record[1]),
  197. 'y': float(record[2]),
  198. 'z': float(record[3]),
  199. 'cell_type': int(record[4]),
  200. })
  201. except (ValueError, IndexError) as e:
  202. print(f"Error processing network record: {record} - {e}")
  203. elif network_data.ndim == 2 and network_data.shape[1] == 4:
  204. for record in network_data:
  205. try:
  206. network_json.append({
  207. 'id': int(record[0]),
  208. 'x': float(record[1]),
  209. 'y': float(record[2]),
  210. 'z': float(record[3])
  211. })
  212. except (ValueError, IndexError) as e:
  213. print(f"Error processing network record: {record} - {e}")
  214. else:
  215. print(f"Dataset 'network' does not have 4 or 5 columns as expected. Found shape: {network_data.shape}")
  216. # Save network JSON (only if we parsed something)
  217. if len(network_json) > 0:
  218. with open(network_json_path, 'w') as json_file:
  219. json.dump(network_json, json_file)
  220. else:
  221. print("Dataset 'network' not found in HDF5 file.")
  222. # Process the 'spikes' or 'activity' dataset
  223. if 'spikes' in hdf_file or 'activity' in hdf_file:
  224. dataset_name = 'spikes' if 'spikes' in hdf_file else 'activity'
  225. spikes_data = hdf_file[dataset_name][:]
  226. if spikes_data.shape[1] == 2:
  227. # Round second column (index 1)
  228. spikes_data[:, 1] = np.round(spikes_data[:, 1], decimals=1) # or change decimals=X as needed
  229. df = pd.DataFrame(spikes_data)
  230. df.to_csv(activity_csv_path, index=False, header=False)
  231. else:
  232. print(f"Dataset '{dataset_name}' does not have 2 columns as expected.")
  233. else:
  234. print("Neither 'spikes' nor 'activity' dataset found in HDF5 file.")
  235. ################### SONATA Implemenation #####################
  236. def _safe_name(name: str) -> str:
  237. name = name.replace("\\", "/").split("/")[-1]
  238. name = re.sub(r"[^a-zA-Z0-9._-]+", "_", name)
  239. return name or "upload.bin"
  240. def _detect_h5_role(h5_path: str) -> str:
  241. with h5py.File(h5_path, "r") as f:
  242. keys = set(f.keys())
  243. if "nodes" in keys:
  244. return "nodes"
  245. if "spikes" in keys:
  246. return "spikes"
  247. return "unknown"
  248. def _detect_node_types_csv(csv_path: str) -> bool:
  249. # supports comma or whitespace separated
  250. try:
  251. df = pd.read_csv(csv_path)
  252. if "node_type_id" in df.columns and "ei" in df.columns:
  253. return True
  254. except Exception:
  255. pass
  256. try:
  257. df = pd.read_csv(csv_path, sep=r"\s+", engine="python")
  258. return ("node_type_id" in df.columns and "ei" in df.columns)
  259. except Exception:
  260. return False
  261. @csrf_exempt
  262. def upload_and_save_sonata_anynames(request):
  263. """
  264. Upload any SONATA files (any names).
  265. - H5 role detected by HDF5 keys: 'nodes' or 'spikes'
  266. - node types CSV detected by columns: node_type_id + ei
  267. """
  268. try:
  269. chunk = request.FILES.get("file")
  270. unique_directory = request.POST.get("uniqueDirectory")
  271. chunk_index = int(request.POST.get("chunkIndex", 0))
  272. total_chunks = int(request.POST.get("totalChunks", 1))
  273. original_name = request.POST.get("originalName") or (chunk.name if chunk else "")
  274. if not chunk or not unique_directory:
  275. return JsonResponse({"error": "Missing file or uniqueDirectory"}, status=400)
  276. base_dir = os.path.join(settings.MEDIA_ROOT, "csv", unique_directory)
  277. os.makedirs(base_dir, exist_ok=True)
  278. safe = _safe_name(original_name)
  279. # store as parts (order independent)
  280. parts_dir = os.path.join(base_dir, f"parts_{safe}")
  281. os.makedirs(parts_dir, exist_ok=True)
  282. part_path = os.path.join(parts_dir, f"part_{chunk_index:08d}.bin")
  283. with open(part_path, "wb") as f:
  284. for piece in chunk.chunks():
  285. f.write(piece)
  286. expected = [os.path.join(parts_dir, f"part_{i:08d}.bin") for i in range(total_chunks)]
  287. if not all(os.path.exists(p) for p in expected):
  288. return JsonResponse({"ok": True, "assembled": False})
  289. # assemble to temp file
  290. tmp_path = os.path.join(base_dir, f"__tmp__{safe}")
  291. with open(tmp_path, "wb") as out:
  292. for p in expected:
  293. with open(p, "rb") as inp:
  294. shutil.copyfileobj(inp, out, length=1024 * 1024)
  295. # cleanup parts
  296. for p in expected:
  297. os.remove(p)
  298. os.rmdir(parts_dir)
  299. # classify by content and rename canonically
  300. lower = safe.lower()
  301. if lower.endswith((".h5", ".hdf5")):
  302. role = _detect_h5_role(tmp_path)
  303. if role == "nodes":
  304. final = os.path.join(base_dir, "nodes.h5")
  305. elif role == "spikes":
  306. final = os.path.join(base_dir, "spikes.h5")
  307. else:
  308. os.remove(tmp_path)
  309. return JsonResponse({"error": f"Uploaded H5 is neither nodes nor spikes: {safe}"}, status=400)
  310. os.replace(tmp_path, final)
  311. return JsonResponse({"ok": True, "assembled": True, "saved_as": os.path.basename(final)})
  312. elif lower.endswith(".csv"):
  313. # detect node_types.csv
  314. if _detect_node_types_csv(tmp_path):
  315. final = os.path.join(base_dir, "internal_node_types.csv")
  316. os.replace(tmp_path, final)
  317. return JsonResponse({"ok": True, "assembled": True, "saved_as": os.path.basename(final)})
  318. else:
  319. # keep it but don't block
  320. final = os.path.join(base_dir, safe)
  321. os.replace(tmp_path, final)
  322. return JsonResponse({"ok": True, "assembled": True, "saved_as": os.path.basename(final)})
  323. else:
  324. # save other files (not needed for NCV)
  325. final = os.path.join(base_dir, safe)
  326. os.replace(tmp_path, final)
  327. return JsonResponse({"ok": True, "assembled": True, "saved_as": os.path.basename(final)})
  328. except Exception as e:
  329. return JsonResponse({"error": str(e)}, status=500)
  330. def pick_population(names):
  331. if not names:
  332. return None
  333. lower = {n.lower(): n for n in names}
  334. return lower.get("ca1", names[0])
  335. def _read_node_types_csv_any_delim(csv_path: str) -> pd.DataFrame:
  336. """
  337. Reads SONATA node_types CSV that may be comma-separated OR whitespace-separated.
  338. Must contain columns: node_type_id, ei
  339. """
  340. # Try comma separated
  341. try:
  342. df = pd.read_csv(csv_path)
  343. if "node_type_id" in df.columns and "ei" in df.columns:
  344. return df
  345. except Exception:
  346. pass
  347. # Fallback: whitespace-separated
  348. df = pd.read_csv(csv_path, sep=r"\s+", engine="python")
  349. return df
  350. def sonata_nodes_to_network_json(nodes_h5_path, out_dir, preferred_pop=None):
  351. """
  352. SONATA nodes.h5 -> NCV network.json
  353. Coordinates:
  354. - nodes/<pop>/x,y,z
  355. - OR nodes/<pop>/positions (Nx3)
  356. - OR nodes/<pop>/<group_id>/positions using node_group_id + node_group_index
  357. Cell types:
  358. - If internal_node_types.csv exists and has (node_type_id, ei):
  359. ei == 'e' -> cell_type = 0
  360. ei == 'i' -> cell_type = 1
  361. - Otherwise: DEFAULT ALL to excitatory (cell_type = 0)
  362. (NO heuristic guessing)
  363. """
  364. os.makedirs(out_dir, exist_ok=True)
  365. out_path = os.path.join(out_dir, "network.json")
  366. def pick_population(names):
  367. if not names:
  368. return None
  369. if preferred_pop:
  370. lower = {n.lower(): n for n in names}
  371. for p in preferred_pop:
  372. if p.lower() in lower:
  373. return lower[p.lower()]
  374. return names[0]
  375. # --- OPTIONAL E/I mapping from CSV ---
  376. type_map = {}
  377. node_types_csv = os.path.join(os.path.dirname(nodes_h5_path), "internal_node_types.csv")
  378. if os.path.exists(node_types_csv):
  379. try:
  380. df = _read_node_types_csv_any_delim(node_types_csv)
  381. if "node_type_id" in df.columns and "ei" in df.columns:
  382. for _, row in df.iterrows():
  383. try:
  384. nid = int(row["node_type_id"])
  385. ei = str(row["ei"]).strip().lower()
  386. if ei == "e":
  387. type_map[nid] = 0
  388. elif ei == "i":
  389. type_map[nid] = 1
  390. except Exception:
  391. pass
  392. except Exception:
  393. # If CSV fails to parse, just ignore and default to excitatory
  394. type_map = {}
  395. with h5py.File(nodes_h5_path, "r") as f:
  396. if "nodes" not in f:
  397. raise ValueError("nodes.h5 missing top-level group 'nodes'")
  398. pop = pick_population(list(f["nodes"].keys()))
  399. if pop is None:
  400. raise ValueError("No population found under /nodes")
  401. popg = f["nodes"][pop]
  402. node_id = popg["node_id"][:]
  403. node_type_id = popg["node_type_id"][:] if "node_type_id" in popg else None
  404. node_group_id = popg["node_group_id"][:] if "node_group_id" in popg else None
  405. node_group_index = popg["node_group_index"][:] if "node_group_index" in popg else None
  406. n = len(node_id)
  407. # ---- COORDINATES ----
  408. if all(k in popg for k in ("x", "y", "z")):
  409. x = popg["x"][:]
  410. y = popg["y"][:]
  411. z = popg["z"][:]
  412. elif "positions" in popg:
  413. pos = popg["positions"][:]
  414. if pos.ndim != 2 or pos.shape[1] < 3:
  415. raise ValueError(f"nodes/{pop}/positions has wrong shape: {pos.shape}")
  416. x, y, z = pos[:, 0], pos[:, 1], pos[:, 2]
  417. else:
  418. # subgroup storage: nodes/<pop>/<group_id>/positions
  419. if node_group_id is None or node_group_index is None:
  420. raise ValueError(
  421. f"Could not find coordinates for nodes/{pop}. "
  422. f"Expected x/y/z or positions Nx3 OR node_group_id & node_group_index."
  423. )
  424. x = np.empty(n, dtype=np.float64)
  425. y = np.empty(n, dtype=np.float64)
  426. z = np.empty(n, dtype=np.float64)
  427. for gid in np.unique(node_group_id):
  428. gid_str = str(int(gid))
  429. if gid_str not in popg:
  430. raise ValueError(f"Group '{gid_str}' not found under nodes/{pop}")
  431. gg = popg[gid_str]
  432. if "positions" in gg:
  433. pos = gg["positions"][:]
  434. if pos.ndim != 2 or pos.shape[1] < 3:
  435. raise ValueError(f"nodes/{pop}/{gid_str}/positions has wrong shape: {pos.shape}")
  436. gx, gy, gz = pos[:, 0], pos[:, 1], pos[:, 2]
  437. elif all(k in gg for k in ("x", "y", "z")):
  438. gx, gy, gz = gg["x"][:], gg["y"][:], gg["z"][:]
  439. else:
  440. raise ValueError(
  441. f"Could not find coordinates in nodes/{pop}/{gid_str}. "
  442. f"Expected positions or x/y/z."
  443. )
  444. mask = (node_group_id == gid)
  445. idxs = node_group_index[mask].astype(np.int64)
  446. if idxs.size == 0:
  447. continue
  448. if idxs.min() < 0 or idxs.max() >= len(gx):
  449. raise ValueError(
  450. f"node_group_index out of range for group {gid_str}: "
  451. f"min={idxs.min()}, max={idxs.max()}, group_len={len(gx)}"
  452. )
  453. x[mask] = gx[idxs]
  454. y[mask] = gy[idxs]
  455. z[mask] = gz[idxs]
  456. if not np.isfinite(x).all() or not np.isfinite(y).all() or not np.isfinite(z).all():
  457. raise ValueError("Coordinates contain NaN/Inf values.")
  458. # ---- WRITE network.json ----
  459. network = []
  460. for i in range(n):
  461. rec = {
  462. "id": int(node_id[i]),
  463. "x": float(x[i]),
  464. "y": float(y[i]),
  465. "z": float(z[i]),
  466. }
  467. # Default: excitatory
  468. ct = 0
  469. # If node_types CSV mapping exists, apply it (unknown -> excitatory)
  470. if node_type_id is not None and type_map:
  471. raw = int(node_type_id[i])
  472. ct = int(type_map.get(raw, 0))
  473. rec["cell_type"] = ct
  474. network.append(rec)
  475. with open(out_path, "w") as fp:
  476. json.dump(network, fp)
  477. return out_path
  478. def sonata_spikes_to_activity_csv(spikes_h5_path, out_dir):
  479. out_path = os.path.join(out_dir, "activity.csv")
  480. with h5py.File(spikes_h5_path, "r") as f:
  481. if "spikes" not in f:
  482. raise ValueError("spikes.h5 missing top-level group 'spikes'")
  483. pop = pick_population(list(f["spikes"].keys()))
  484. g = f["spikes"][pop]
  485. # common names
  486. if "timestamps" in g:
  487. times = np.array(g["timestamps"][:], dtype=np.float64)
  488. elif "times" in g:
  489. times = np.array(g["times"][:], dtype=np.float64)
  490. elif "time" in g:
  491. times = np.array(g["time"][:], dtype=np.float64)
  492. else:
  493. raise ValueError(f"spikes/{pop} missing timestamps dataset")
  494. if "node_ids" in g:
  495. node_ids = np.array(g["node_ids"][:], dtype=np.int64)
  496. elif "node_id" in g:
  497. node_ids = np.array(g["node_id"][:], dtype=np.int64)
  498. else:
  499. raise ValueError(f"spikes/{pop} missing node_ids dataset")
  500. # heuristic seconds->ms
  501. if len(times) and times.max() < 1000.0:
  502. times = times * 1000.0
  503. # times = np.round(times, 1)
  504. times = np.round(times, 3)
  505. pd.DataFrame({"id": node_ids, "t": times}).to_csv(out_path, index=False, header=False)
  506. return out_path
  507. @csrf_exempt
  508. def fetch_user_sonata_data(request):
  509. try:
  510. unique_directory = request.GET.get("dir")
  511. if not unique_directory:
  512. return JsonResponse({"error": "Missing directory identifier"}, status=400)
  513. out_dir = os.path.join(settings.MEDIA_ROOT, "csv", unique_directory)
  514. if not os.path.exists(out_dir):
  515. return JsonResponse({"error": "Directory not found"}, status=404)
  516. nodes_path = os.path.join(out_dir, "nodes.h5")
  517. spikes_path = os.path.join(out_dir, "spikes.h5")
  518. if not os.path.exists(nodes_path) or not os.path.exists(spikes_path):
  519. return JsonResponse({"error": "nodes.h5 and spikes.h5 are required"}, status=400)
  520. # 1) Convert SONATA -> NCV files
  521. network_json_path = os.path.join(out_dir, "network.json")
  522. activity_csv_path = os.path.join(out_dir, "activity.csv")
  523. # Always regenerate derived files
  524. if os.path.exists(network_json_path):
  525. os.remove(network_json_path)
  526. if os.path.exists(activity_csv_path):
  527. os.remove(activity_csv_path)
  528. # remove old chunks
  529. for fn in os.listdir(out_dir):
  530. if fn.startswith("activity_chunk_") and fn.endswith(".json"):
  531. os.remove(os.path.join(out_dir, fn))
  532. # ---- Determine whether a valid node_types CSV exists (and likely usable) ----
  533. # upload endpoint stores a detected one as internal_node_types.csv
  534. node_types_csv = os.path.join(out_dir, "internal_node_types.csv")
  535. used_cell_types_csv = False
  536. if os.path.exists(node_types_csv):
  537. try:
  538. df = _read_node_types_csv_any_delim(node_types_csv)
  539. if "node_type_id" in df.columns and "ei" in df.columns:
  540. # "used" if it contains at least one E or I mapping
  541. eis = set(str(v).strip().lower() for v in df["ei"].dropna().tolist())
  542. used_cell_types_csv = ("e" in eis) or ("i" in eis)
  543. except Exception:
  544. used_cell_types_csv = False
  545. # Generate network.json + activity.csv
  546. sonata_nodes_to_network_json(nodes_path, out_dir)
  547. sonata_spikes_to_activity_csv(spikes_path, out_dir)
  548. # 2) Load network.json for visualization
  549. with open(network_json_path, "r") as f:
  550. file_data = ujson.load(f)
  551. total_neurons = len(file_data)
  552. total_excitatory = 0
  553. total_inhibitory = 0
  554. # cell_type counts derived from network.json (source of truth)
  555. for neuro in file_data:
  556. ct = neuro.get("cell_type", 0)
  557. if ct == 0:
  558. total_excitatory += 1
  559. elif ct == 1:
  560. total_inhibitory += 1
  561. # 3) Chunk activity.csv and compute MFR_total + MFR_active
  562. lstresponse = {}
  563. flag_check = 0
  564. if os.path.exists(activity_csv_path):
  565. flag_check = 1
  566. lstresponse = chunk_activity_csv(
  567. activity_csv_path,
  568. out_dir,
  569. chunk_size_bytes=100 * 1024 * 1024,
  570. network_total_neurons=total_neurons,
  571. )
  572. data_list = [{
  573. "filename": "network.json",
  574. "fileData": file_data,
  575. "total_neurons": total_neurons,
  576. "total_excitatory": total_excitatory,
  577. "total_inhibitory": total_inhibitory,
  578. "json_directory": out_dir,
  579. "cell_type_mode": "csv" if used_cell_types_csv else "default_all_excitatory",
  580. }]
  581. response_data = {
  582. "files": data_list,
  583. "lstresponse": lstresponse,
  584. "total_size": (os.path.getsize(network_json_path) / (1024 * 1024)) if os.path.exists(network_json_path) else 0,
  585. "flag_check": flag_check,
  586. "used_cell_types_csv": used_cell_types_csv, # ✅ NEW FLAG for UI
  587. }
  588. return JsonResponse(response_data, safe=False)
  589. except Exception as e:
  590. return JsonResponse({"error": str(e)}, status=500)
  591. def chunk_activity_csv(
  592. activity_csv_path,
  593. out_dir,
  594. chunk_size_bytes=100 * 1024 * 1024,
  595. network_total_neurons=None,
  596. ):
  597. """
  598. Produces activity_chunk_N.json and returns lstresponse dict.
  599. Adds:
  600. - MFR_total (normalized by total network neurons)
  601. - MFR_active (normalized by active neurons in chunk)
  602. - n_total, n_active, total_spikes, duration_ms
  603. """
  604. try:
  605. try:
  606. csv_data = pd.read_csv(activity_csv_path, header=None, names=["id", "t"], encoding="utf-8")
  607. except UnicodeDecodeError:
  608. csv_data = pd.read_csv(activity_csv_path, header=None, names=["id", "t"], encoding="latin1")
  609. # group by time
  610. data_dict = csv_data.groupby("t")["id"].agg(list).to_dict()
  611. chunk_number = 0
  612. first_last_keys = []
  613. chunk_mfr_list = []
  614. current_chunk_size = 0
  615. current_data_dict = {}
  616. # total neurons for MFR_total
  617. n_total = int(network_total_neurons) if network_total_neurons and int(network_total_neurons) > 0 else None
  618. def finalize_chunk(chunk_data, chunk_number):
  619. times_in_chunk = sorted(chunk_data.keys())
  620. t_min = float(times_in_chunk[0])
  621. t_max = float(times_in_chunk[-1])
  622. duration_ms = t_max - t_min
  623. total_time_sec = max(duration_ms / 1000.0, 1e-9)
  624. total_spikes = sum(len(v) for v in chunk_data.values())
  625. unique_neurons = set(gid for v in chunk_data.values() for gid in v)
  626. n_active = len(unique_neurons) or 1
  627. n_total_used = n_total if n_total is not None else n_active
  628. mfr_total = round((total_spikes / n_total_used) / total_time_sec, 2)
  629. mfr_active = round((total_spikes / n_active) / total_time_sec, 2)
  630. # save chunk json
  631. chunk_file_path = os.path.join(out_dir, f"activity_chunk_{chunk_number}.json")
  632. with open(chunk_file_path, "w") as f:
  633. ujson.dump(chunk_data, f)
  634. meta = {
  635. "chunk_number": chunk_number,
  636. "first_key": t_min,
  637. "last_key": t_max,
  638. "duration_ms": round(duration_ms, 1),
  639. "total_spikes": int(total_spikes),
  640. "n_total": int(n_total_used),
  641. "n_active": int(n_active),
  642. "MFR_total": mfr_total,
  643. "MFR_active": mfr_active,
  644. }
  645. return meta
  646. # IMPORTANT: ensure time sorted iteration
  647. for key in sorted(data_dict.keys()):
  648. values = data_dict[key]
  649. entry_size = len(ujson.dumps({key: values}))
  650. if current_chunk_size + entry_size > chunk_size_bytes and current_data_dict:
  651. meta = finalize_chunk(current_data_dict, chunk_number)
  652. chunk_mfr_list.append(meta)
  653. first_last_keys.append({
  654. "chunk_number": chunk_number,
  655. "first_key": meta["first_key"],
  656. "last_key": meta["last_key"],
  657. })
  658. current_data_dict = {}
  659. current_chunk_size = 0
  660. chunk_number += 1
  661. current_data_dict[key] = values
  662. current_chunk_size += entry_size
  663. if current_data_dict:
  664. meta = finalize_chunk(current_data_dict, chunk_number)
  665. chunk_mfr_list.append(meta)
  666. first_last_keys.append({
  667. "chunk_number": chunk_number,
  668. "first_key": meta["first_key"],
  669. "last_key": meta["last_key"],
  670. })
  671. total_activity_files = chunk_number + 1
  672. return {
  673. "OUTPUT_FOLDER": out_dir,
  674. "total_lines": int(len(csv_data)),
  675. "total_activity_files": int(total_activity_files),
  676. "first_last_keys": first_last_keys,
  677. "chunk_mfr_list": chunk_mfr_list,
  678. }
  679. except Exception as e:
  680. raise RuntimeError(f"Error processing activity file: {str(e)}")
  681. ############################################################
  682. @csrf_exempt
  683. def fetch_user_network_data(request):
  684. try:
  685. unique_directory = request.GET.get('dir')
  686. if not unique_directory:
  687. return JsonResponse({'error': 'Missing directory identifier'}, status=400)
  688. json_directory = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  689. if not os.path.exists(json_directory):
  690. return JsonResponse({'error': 'Directory not found'}, status=404)
  691. data_list = []
  692. lstresponse = []
  693. total_size = 0
  694. flag_check = 0
  695. network_file_path = os.path.join(json_directory, "network.json")
  696. if os.path.exists(network_file_path):
  697. with open(network_file_path, 'r') as f:
  698. file_data = ujson.load(f)
  699. total_excitatory = 0
  700. total_inhibitory = 0
  701. total_invalid = 0
  702. network_total_neurons = len(file_data)
  703. # ---- NEW: default ratio when cell_type missing ----
  704. excit_ratio = 90.909533 / 100.0
  705. target_excitatory = int(round(network_total_neurons * excit_ratio))
  706. # ---------------------------------------------------
  707. for i, neuro in enumerate(file_data):
  708. cell_type = neuro.get('cell_type', None)
  709. # ---- VALIDATION / DEFAULT ----
  710. if cell_type is None:
  711. if total_excitatory < target_excitatory:
  712. total_excitatory += 1
  713. else:
  714. total_inhibitory += 1
  715. continue
  716. # ------------------------------
  717. if cell_type == 0:
  718. total_excitatory += 1
  719. elif cell_type == 1:
  720. total_inhibitory += 1
  721. else:
  722. print(f"Warning: neuron {i} unknown cell_type={cell_type}")
  723. total_invalid += 1
  724. # print("excit:", total_excitatory)
  725. # print("inh:", total_inhibitory)
  726. # print("cell_type:", cell_type)
  727. data_list.append({
  728. 'filename': "network.json",
  729. 'fileData': file_data,
  730. 'total_neurons': network_total_neurons,
  731. 'total_excitatory': total_excitatory,
  732. 'total_inhibitory': total_inhibitory,
  733. # 'total_invalid': total_invalid,
  734. 'json_directory': json_directory,
  735. 'cell_type':cell_type,
  736. })
  737. total_size += (os.path.getsize(network_file_path) / (1024 * 1024))
  738. activity_file_path = os.path.join(json_directory, "activity.csv")
  739. if os.path.exists(activity_file_path):
  740. flag_check = 1
  741. try:
  742. CHUNK_SIZE = 200 * 1024 * 1024
  743. try:
  744. csv_data = pd.read_csv(activity_file_path, header=None, names=['id', 't'], encoding='utf-8')
  745. except UnicodeDecodeError:
  746. csv_data = pd.read_csv(activity_file_path, header=None, names=['id', 't'], encoding='latin1')
  747. data_dict = csv_data.groupby('t')['id'].agg(list).to_dict()
  748. chunk_number = 0
  749. first_last_keys = []
  750. chunk_mfr_list = []
  751. current_chunk_size = 0
  752. current_data_dict = {}
  753. for key in sorted(data_dict.keys()):
  754. values = data_dict[key]
  755. entry_size = len(ujson.dumps({key: values}))
  756. if current_chunk_size + entry_size > CHUNK_SIZE and current_data_dict:
  757. times_in_chunk = list(current_data_dict.keys())
  758. t_min = float(times_in_chunk[0])
  759. t_max = float(times_in_chunk[-1])
  760. total_time_sec = (t_max - t_min) / 1000.0
  761. total_spikes = sum(len(v) for v in current_data_dict.values())
  762. # active neurons in this chunk (optional, keep if you want)
  763. unique_neurons = set(gid for v in current_data_dict.values() for gid in v)
  764. n_active = len(unique_neurons) or 1
  765. # total neurons from network.json (this is what you want for dropdown)
  766. n_total = network_total_neurons if network_total_neurons > 0 else n_active
  767. mfr_total = round((total_spikes / n_total) / total_time_sec, 2)
  768. mfr_active = round((total_spikes / n_active) / total_time_sec, 2)
  769. chunk_mfr_list.append({
  770. "first_key": t_min,
  771. "last_key": t_max,
  772. "chunk_number": chunk_number,
  773. "MFR_total": mfr_total,
  774. "MFR_active": mfr_active,
  775. "n_total": int(n_total),
  776. "n_active": int(n_active),
  777. "total_spikes": int(total_spikes),
  778. })
  779. first_last_keys.append({'chunk_number': chunk_number, 'first_key': t_min, 'last_key': t_max})
  780. chunk_file_path = os.path.join(json_directory, f'activity_chunk_{chunk_number}.json')
  781. with open(chunk_file_path, 'w') as f:
  782. ujson.dump(current_data_dict, f)
  783. current_data_dict = {}
  784. current_chunk_size = 0
  785. chunk_number += 1
  786. current_data_dict[key] = values
  787. current_chunk_size += entry_size
  788. if current_data_dict:
  789. times_in_chunk = list(current_data_dict.keys())
  790. t_min = float(times_in_chunk[0])
  791. t_max = float(times_in_chunk[-1])
  792. total_time_sec = (t_max - t_min) / 1000.0
  793. total_spikes = sum(len(v) for v in current_data_dict.values())
  794. # active neurons in this chunk (optional, keep if you want)
  795. unique_neurons = set(gid for v in current_data_dict.values() for gid in v)
  796. n_active = len(unique_neurons) or 1
  797. # total neurons from network.json (this is what you want for dropdown)
  798. n_total = network_total_neurons if network_total_neurons > 0 else n_active
  799. mfr_total = round((total_spikes / n_total) / total_time_sec, 2)
  800. mfr_active = round((total_spikes / n_active) / total_time_sec, 2)
  801. chunk_mfr_list.append({
  802. "first_key": t_min,
  803. "last_key": t_max,
  804. "chunk_number": chunk_number,
  805. "MFR_total": mfr_total,
  806. "MFR_active": mfr_active,
  807. "n_total": int(n_total),
  808. "n_active": int(n_active),
  809. "total_spikes": int(total_spikes),
  810. })
  811. first_last_keys.append({'chunk_number': chunk_number, 'first_key': t_min, 'last_key': t_max})
  812. final_chunk_file_path = os.path.join(json_directory, f'activity_chunk_{chunk_number}.json')
  813. with open(final_chunk_file_path, 'w') as f:
  814. ujson.dump(current_data_dict, f)
  815. total_activity_files = chunk_number + 1
  816. lstresponse = {
  817. 'OUTPUT_FOLDER': json_directory,
  818. 'total_lines': len(csv_data),
  819. 'total_activity_files': total_activity_files,
  820. 'first_last_keys': first_last_keys,
  821. 'chunk_mfr_list': chunk_mfr_list
  822. }
  823. except Exception as e:
  824. return JsonResponse({'error': f'Error processing activity file: {str(e)}'}, status=500)
  825. response_data = {
  826. 'files': data_list,
  827. 'lstresponse': lstresponse,
  828. 'total_size': total_size,
  829. 'flag_check': flag_check
  830. }
  831. return JsonResponse(response_data, safe=False, content_type='application/json')
  832. except Exception as e:
  833. return JsonResponse({'error': str(e)}, status=500)
  834. # def get_chunk_data(request):
  835. # chunk_number = request.GET.get("chunk_number") # Get chunk number from request
  836. # unique_directory = request.GET.get("dir") # Get chunk number from request
  837. # if not chunk_number:
  838. # return JsonResponse({"error": "No chunk number provided"}, status=400)
  839. # chunk_file_path = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  840. # unique_directory = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  841. # activity_file_path = os.path.join(chunk_file_path, f"activity_chunk_{chunk_number}.json")
  842. # try:
  843. # with open(activity_file_path, "r") as f:
  844. # chunk_data = ujson.load(f)
  845. # except Exception as e:
  846. # return JsonResponse({"error": f"Error loading activity.json: {str(e)}"}, status=500)
  847. # # Process GIDs from network.json
  848. # network_file_path = os.path.join(unique_directory, "network.json")
  849. # Gids_array = extract_ids_from_json(network_file_path)
  850. # gids_activity_indexes = get_activity_indexes(chunk_data, Gids_array)
  851. # # print("length gids: ",len(gids_activity_indexes))
  852. # # Cache GIDs activity indexes
  853. # cache_file_path = os.path.join(chunk_file_path, 'gids_activity_indexes.json')
  854. # try:
  855. # with open(cache_file_path, 'w') as f:
  856. # ujson.dump(gids_activity_indexes, f)
  857. # except Exception as e:
  858. # return JsonResponse({"error": f"Error saving gids_activity_indexes.json: {str(e)}"}, status=500)
  859. # # Extract `t` values (time) and count spikes per time bin
  860. # try:
  861. # total_spikes = sum(len(activity_ids) for activity_ids in chunk_data.values())
  862. # # Generate visualization images
  863. # # raster_img = generate_raster_plot(chunk_data)
  864. # hist_img = generate_spike_histogram(chunk_data, chunk_number)
  865. # # in get_chunk_data(...) right before the final JsonResponse
  866. # hist = compute_hist_data(chunk_data, chunk_number, bins=100)
  867. # # Return both chunk data and GIDs activity indexes in a single response
  868. # return JsonResponse({
  869. # "chunk_data": chunk_data,
  870. # "total_spikes": total_spikes, # Total spikes count
  871. # # "raster_plot": raster_img,
  872. # "histogram": hist_img,
  873. # "hist_data": hist, # NEW: JSON for interactive plot
  874. # "gids_activity_indexes": gids_activity_indexes
  875. # })
  876. # except Exception as e:
  877. # return JsonResponse({"error": f"Error generating histogram: {str(e)}"}, status=500)
  878. def get_chunk_data(request):
  879. chunk_number = request.GET.get("chunk_number") # Get chunk number from request
  880. unique_directory = request.GET.get("dir") # Get chunk number from request
  881. if not chunk_number:
  882. return JsonResponse({"error": "No chunk number provided"}, status=400)
  883. chunk_file_path = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  884. unique_directory = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  885. activity_file_path = os.path.join(chunk_file_path, f"activity_chunk_{chunk_number}.json")
  886. try:
  887. with open(activity_file_path, "r") as f:
  888. chunk_data = ujson.load(f)
  889. except Exception as e:
  890. return JsonResponse({"error": f"Error loading activity.json: {str(e)}"}, status=500)
  891. # Process GIDs from network.json
  892. network_file_path = os.path.join(unique_directory, "network.json")
  893. Gids_array = extract_ids_from_json(network_file_path)
  894. gids_activity_indexes = get_activity_indexes(chunk_data, Gids_array)
  895. # print("length gids: ",len(gids_activity_indexes))
  896. # --- after you load Gids_array ---
  897. total_neurons = len(Gids_array) # total neurons in the uploaded network
  898. # Cache GIDs activity indexes
  899. cache_file_path = os.path.join(chunk_file_path, 'gids_activity_indexes.json')
  900. try:
  901. with open(cache_file_path, 'w') as f:
  902. ujson.dump(gids_activity_indexes, f)
  903. except Exception as e:
  904. return JsonResponse({"error": f"Error saving gids_activity_indexes.json: {str(e)}"}, status=500)
  905. # Extract `t` values (time) and count spikes per time bin
  906. try:
  907. total_spikes = sum(len(activity_ids) for activity_ids in chunk_data.values())
  908. # Generate visualization images
  909. # raster_img = generate_raster_plot(chunk_data)
  910. # Pass total_neurons so UI MFR matches benchmark definition
  911. hist_img = generate_spike_histogram(chunk_data, chunk_number, color="white", total_neurons=total_neurons)
  912. # Interactive histogram JSON (includes MFR values)
  913. hist = compute_hist_data(chunk_data, chunk_number, bins=100, total_neurons=total_neurons)
  914. # Return both chunk data and GIDs activity indexes in a single response
  915. return JsonResponse({
  916. "chunk_data": chunk_data,
  917. "total_spikes": total_spikes, # Total spikes count
  918. # "raster_plot": raster_img,
  919. "histogram": hist_img,
  920. "hist_data": hist, # NEW: JSON for interactive plot
  921. "gids_activity_indexes": gids_activity_indexes,
  922. "total_neurons": total_neurons, # expose to frontend
  923. })
  924. except Exception as e:
  925. return JsonResponse({"error": f"Error generating histogram: {str(e)}"}, status=500)
  926. def generate_raster_plot(chunk_data):
  927. try:
  928. if not chunk_data:
  929. return "Error: No data available for plotting."
  930. spike_times = []
  931. for t_str, gids in chunk_data.items():
  932. t = float(t_str)
  933. for gid in gids:
  934. spike_times.append((t, gid))
  935. # Convert to DataFrame
  936. raster_df = pd.DataFrame(spike_times, columns=["t", "gid"])
  937. # Create raster plot
  938. plt.figure(figsize=(12, 6))
  939. plt.scatter(raster_df["t"], raster_df["gid"], s=2, color="darkred", alpha=0.7)
  940. plt.title("Raster Plot of Spike Activity")
  941. plt.xlabel("Time (ms)")
  942. plt.ylabel("Neuron GID")
  943. plt.grid(True, linestyle="--", alpha=0.5)
  944. plt.tight_layout()
  945. plt.show()
  946. # Save as High-Resolution Image
  947. img_buffer = io.BytesIO()
  948. plt.savefig(img_buffer, format="png", bbox_inches="tight", dpi=900)
  949. img_buffer.seek(0)
  950. img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")
  951. return img_base64
  952. except Exception as e:
  953. return f"Error generating raster plot: {str(e)}"
  954. # def compute_hist_data(chunk_data, chunk_number, bins=100, total_neurons=None):
  955. # # flatten (t,gids)
  956. # times = []
  957. # times_gids = [] # list of (t, set(gids)) per unique t
  958. # gid_set = set()
  959. # for t_str, gids in chunk_data.items():
  960. # t = float(t_str)
  961. # gids_set = set(gids)
  962. # gid_set |= gids_set
  963. # times.extend([t] * len(gids))
  964. # times_gids.append((t, gids_set))
  965. # if not times:
  966. # return {"bin_edges": [], "bin_centers": [], "counts": [], "sd_poisson": [], "sd_binomial": [], "t_min": 0, "t_max": 0, "mfr": 0}
  967. # t_min = 0.0 if str(chunk_number) == "0" else min(times)
  968. # t_max = max(times)
  969. # edges = np.linspace(t_min, t_max, bins + 1)
  970. # counts, _ = np.histogram(times, bins=edges)
  971. # # Poisson SD
  972. # sd_poisson = np.sqrt(counts)
  973. # # Binomial SD (unique neurons per bin)
  974. # N = total_neurons if total_neurons is not None else (len(gid_set) or 1)
  975. # uniq_per_bin = np.zeros(bins, dtype=int)
  976. # # bucket times_gids by bin (fast pass)
  977. # for t, gids in times_gids:
  978. # if t < t_min or t >= t_max:
  979. # continue
  980. # i = np.searchsorted(edges, t, side='right') - 1
  981. # if 0 <= i < bins:
  982. # # accumulate per-bin unique neurons
  983. # # WARNING: for huge data this set-per-bin can be heavy;
  984. # # replace with a counting bloom or skip if not needed.
  985. # pass
  986. # # Memory-light alternative: approximate k with counts capped at 1 per neuron per bin
  987. # # If you can compute uniqs, do:
  988. # # p = uniq_per_bin / float(N)
  989. # # sd_binomial = np.sqrt(N * p * (1.0 - p))
  990. # sd_binomial = None # optional
  991. # total_spikes = len(times)
  992. # total_time_sec = max((t_max - t_min) / 1000.0, 1e-9)
  993. # mfr = (total_spikes / max(N, 1)) / total_time_sec
  994. # return {
  995. # "bin_edges": edges.tolist(),
  996. # "bin_centers": ((edges[:-1] + edges[1:]) / 2.0).tolist(),
  997. # "counts": counts.tolist(),
  998. # "sd_poisson": sd_poisson.tolist(),
  999. # "sd_binomial": sd_binomial.tolist() if sd_binomial is not None else [],
  1000. # "t_min": float(t_min),
  1001. # "t_max": float(t_max),
  1002. # "mfr": float(mfr),
  1003. # }
  1004. def compute_hist_data(chunk_data, chunk_number, bins=100, total_neurons=None):
  1005. # flatten all spikes
  1006. all_times = []
  1007. active_neurons = set()
  1008. for t_str, gids in chunk_data.items():
  1009. t = float(t_str)
  1010. all_times.extend([t] * len(gids))
  1011. active_neurons.update(gids)
  1012. if not all_times:
  1013. return {"bin_edges": [], "bin_centers": [], "counts": [], "sd_poisson": [],
  1014. "t_min": 0.0, "t_max": 0.0, "mfr_total": 0.0, "mfr_active": 0.0,
  1015. "n_total": int(total_neurons or 0), "n_active": 0, "total_spikes": 0}
  1016. t_min, t_max = chunk_time_bounds_from_keys(chunk_data)
  1017. edges = np.linspace(t_min, t_max, bins + 1)
  1018. counts, _ = np.histogram(all_times, bins=edges)
  1019. sd_poisson = np.sqrt(counts)
  1020. total_spikes = len(all_times)
  1021. dur_s = max((t_max - t_min) / 1000.0, 1e-9)
  1022. n_active = max(len(active_neurons), 1)
  1023. n_total = int(total_neurons) if total_neurons and int(total_neurons) > 0 else n_active
  1024. mfr_total = (total_spikes / n_total) / dur_s
  1025. mfr_active = (total_spikes / n_active) / dur_s
  1026. return {
  1027. "bin_edges": edges.tolist(),
  1028. "bin_centers": ((edges[:-1] + edges[1:]) / 2.0).tolist(),
  1029. "counts": counts.tolist(),
  1030. "sd_poisson": sd_poisson.tolist(),
  1031. "t_min": t_min,
  1032. "t_max": t_max,
  1033. "total_spikes": int(total_spikes),
  1034. "n_total": int(n_total),
  1035. "n_active": int(n_active),
  1036. "mfr_total": float(mfr_total),
  1037. "mfr_active": float(mfr_active),
  1038. }
  1039. # def generate_spike_histogram(chunk_data, chunk_number, bins=100, color="white"):
  1040. # try:
  1041. # if not chunk_data or not isinstance(chunk_data, dict):
  1042. # return "Error: No data available for plotting."
  1043. # # Convert spike times and collect active neurons
  1044. # all_spike_times = []
  1045. # active_neurons = set()
  1046. # for t_str, gids in chunk_data.items():
  1047. # t = float(t_str)
  1048. # all_spike_times.extend([t] * len(gids))
  1049. # active_neurons.update(gids)
  1050. # if not all_spike_times:
  1051. # return "Error: No spike times in chunk data."
  1052. # # Sort times to get actual range
  1053. # min_time = min(all_spike_times)
  1054. # max_time = max(all_spike_times)
  1055. # # Set time range based on chunk number
  1056. # if str(chunk_number) == "0":
  1057. # t_min = 0
  1058. # else:
  1059. # t_min = min_time
  1060. # t_max = max_time
  1061. # # Compute histogram
  1062. # bin_edges = np.linspace(t_min, t_max, bins + 1)
  1063. # spike_counts, _ = np.histogram(all_spike_times, bins=bin_edges)
  1064. # # Compute per-bin standard deviation
  1065. # std_per_bin = []
  1066. # for i in range(len(bin_edges) - 1):
  1067. # bin_start, bin_end = bin_edges[i], bin_edges[i + 1]
  1068. # bin_spikes = [t for t in all_spike_times if bin_start <= t < bin_end]
  1069. # if len(bin_spikes) > 1:
  1070. # std_per_bin.append(np.std(bin_spikes))
  1071. # else:
  1072. # std_per_bin.append(0.0) # No variation if ≤1 spike
  1073. # # Compute Mean Firing Rate (MFR)
  1074. # total_spikes = len(all_spike_times)
  1075. # total_neurons = len(active_neurons)
  1076. # total_time_sec = (t_max - t_min) / 1000.0
  1077. # mfr = (total_spikes / total_neurons) / total_time_sec if total_neurons > 0 and total_time_sec > 0 else 0
  1078. # # Plot histogram
  1079. # fig, ax = plt.subplots(figsize=(12, 6), dpi=300)
  1080. # fig.patch.set_facecolor("#33373d")
  1081. # ax.set_facecolor("#33373d")
  1082. # bars = ax.bar(bin_edges[:-1], spike_counts, width=np.diff(bin_edges),
  1083. # color=color, alpha=0.7, edgecolor="black", align="edge")
  1084. # # Annotate each bar with its std
  1085. # for bar, std_val in zip(bars, std_per_bin):
  1086. # height = bar.get_height()
  1087. # ax.text(bar.get_x() + bar.get_width() / 2, height + 0.5,
  1088. # f"{std_val:.1f}", ha="center", va="bottom", fontsize=6, color="yellow", rotation=90)
  1089. # # Legend info
  1090. # legend_labels = {
  1091. # "# Max Spike": max(spike_counts),
  1092. # "# Min Spike": min(spike_counts),
  1093. # "# Spikes": total_spikes,
  1094. # "# Neurons": total_neurons,
  1095. # "# Min Time (ms)": round(t_min, 1),
  1096. # "# Max Time (ms)": round(t_max, 1),
  1097. # }
  1098. # legend_text = "\n".join([f"{key}: {value}" for key, value in legend_labels.items()])
  1099. # plt.gcf().text(0.80, 0.79, legend_text, fontsize=8,
  1100. # bbox=dict(facecolor='white', alpha=0.5), color='black')
  1101. # ax.set_xlim(t_min, t_max)
  1102. # ax.set_xlabel("Time (ms)", fontsize=16, fontweight="bold", color="white")
  1103. # ax.set_ylabel("Number of Firing Neurons", fontsize=16, fontweight="bold", color="white")
  1104. # ax.set_title(f"Chunk {chunk_number} - MFR: {mfr:.2f} Hz", fontsize=18, fontweight="bold", color="white")
  1105. # ax.tick_params(axis='x', colors='white')
  1106. # ax.tick_params(axis='y', colors='white')
  1107. # ax.xaxis.set_major_locator(ticker.MaxNLocator(nbins=10))
  1108. # ax.grid(True, linestyle="--", alpha=0.4)
  1109. # for label in ax.get_xticklabels() + ax.get_yticklabels():
  1110. # label.set_color("white")
  1111. # # Convert to base64 image
  1112. # img_buffer = io.BytesIO()
  1113. # plt.savefig(img_buffer, format="png", bbox_inches="tight", dpi=300, facecolor=fig.get_facecolor())
  1114. # img_buffer.seek(0)
  1115. # img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")
  1116. # plt.close()
  1117. # return img_base64
  1118. # except Exception as e:
  1119. # return f"Error generating spike histogram: {str(e)}"
  1120. def generate_spike_histogram(chunk_data, chunk_number, color="white", total_neurons=None):
  1121. try:
  1122. if not chunk_data or not isinstance(chunk_data, dict):
  1123. return "Error: No data available for plotting."
  1124. all_spike_times = []
  1125. active_neurons = set()
  1126. for t_str, gids in chunk_data.items():
  1127. t = float(t_str)
  1128. all_spike_times.extend([t] * len(gids))
  1129. active_neurons.update(gids)
  1130. if not all_spike_times:
  1131. return "Error: No spike times in chunk data."
  1132. min_time = min(all_spike_times)
  1133. max_time = max(all_spike_times)
  1134. t_min, t_max = chunk_time_bounds_from_keys(chunk_data)
  1135. bin_edges = np.linspace(np.floor(t_min), np.ceil(t_max), np.ceil(t_max)-np.floor(t_min)+1)
  1136. spike_counts, _ = np.histogram(all_spike_times, bins=bin_edges)
  1137. # Std per bin (your existing approach)
  1138. std_per_bin = []
  1139. for i in range(len(bin_edges) - 1):
  1140. bin_start, bin_end = bin_edges[i], bin_edges[i + 1]
  1141. bin_spikes = [t for t in all_spike_times if bin_start <= t < bin_end]
  1142. std_per_bin.append(float(np.std(bin_spikes)) if len(bin_spikes) > 1 else 0.0)
  1143. total_spikes = len(all_spike_times)
  1144. total_time_sec = max((t_max - t_min) / 1000.0, 1e-9)
  1145. n_active = len(active_neurons) or 1
  1146. n_total = int(total_neurons) if (total_neurons is not None and int(total_neurons) > 0) else n_active
  1147. # Two MFRs:
  1148. mfr_total = (total_spikes / n_total) / total_time_sec # matches benchmark definition
  1149. mfr_active = (total_spikes / n_active) / total_time_sec # old behavior
  1150. # Plot
  1151. fig, ax = plt.subplots(figsize=(12, 6), dpi=300)
  1152. fig.patch.set_facecolor("#33373d")
  1153. ax.set_facecolor("#33373d")
  1154. bars = ax.bar(
  1155. bin_edges[:-1], spike_counts, width=np.diff(bin_edges),
  1156. color=color, alpha=0.7, edgecolor="black", align="edge"
  1157. )
  1158. for bar, std_val in zip(bars, std_per_bin):
  1159. height = bar.get_height()
  1160. ax.text(
  1161. bar.get_x() + bar.get_width() / 2, height + 0.5,
  1162. f"{std_val:.1f}", ha="center", va="bottom",
  1163. fontsize=6, color="yellow", rotation=90
  1164. )
  1165. legend_labels = {
  1166. "# Max Spike": int(max(spike_counts)) if len(spike_counts) else 0,
  1167. "# Min Spike": int(min(spike_counts)) if len(spike_counts) else 0,
  1168. "# Spikes": int(total_spikes),
  1169. "# Active Neurons": int(n_active),
  1170. "# Total Neurons": int(n_total),
  1171. "# Min Time (ms)": float(t_min),
  1172. "# Max Time (ms)": float(t_max),
  1173. "MFR (total-norm) Hz": float(mfr_total),
  1174. "MFR (active-norm) Hz": float(mfr_active),
  1175. }
  1176. legend_text = "\n".join([f"{k}: {v}" for k, v in legend_labels.items()])
  1177. plt.gcf().text(0.80, 0.79, legend_text, fontsize=8,
  1178. bbox=dict(facecolor='white', alpha=0.5), color='black')
  1179. ax.set_xlim(t_min, t_max)
  1180. ax.set_xlabel("Time (ms)", fontsize=16, fontweight="bold", color="white")
  1181. ax.set_ylabel("Number of Firing Neurons", fontsize=16, fontweight="bold", color="white")
  1182. # IMPORTANT: title now shows total-normalized MFR (matches benchmark)
  1183. ax.set_title(f"Chunk {chunk_number} - MFR: {mfr_total:.2f} Hz", fontsize=18, fontweight="bold", color="white")
  1184. ax.tick_params(axis='x', colors='white')
  1185. ax.tick_params(axis='y', colors='white')
  1186. ax.xaxis.set_major_locator(ticker.MaxNLocator(nbins=10))
  1187. ax.grid(True, linestyle="--", alpha=0.4)
  1188. for label in ax.get_xticklabels() + ax.get_yticklabels():
  1189. label.set_color("white")
  1190. img_buffer = io.BytesIO()
  1191. plt.savefig(img_buffer, format="png", bbox_inches="tight", dpi=300, facecolor=fig.get_facecolor())
  1192. img_buffer.seek(0)
  1193. img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")
  1194. plt.close()
  1195. return img_base64
  1196. except Exception as e:
  1197. return f"Error generating spike histogram: {str(e)}"
  1198. def chunk_time_bounds_from_keys(chunk_data):
  1199. # chunk_data keys are your time bins (strings)
  1200. ts = sorted(float(k) for k in chunk_data.keys())
  1201. if not ts:
  1202. return 0.0, 0.0
  1203. return ts[0], ts[-1]
  1204. def generate_network_graph(network_data):
  1205. try:
  1206. if not network_data:
  1207. print("No network data available.")
  1208. return None
  1209. # Create a directed graph
  1210. G = nx.DiGraph()
  1211. # Add edges (connections)
  1212. for pre_neuron, post_neurons in network_data.items():
  1213. for post_neuron in post_neurons:
  1214. G.add_edge(pre_neuron, post_neuron)
  1215. # Draw the network
  1216. plt.figure(figsize=(8, 6))
  1217. pos = nx.spring_layout(G, seed=42) # Layout for better visualization
  1218. nx.draw(G, pos, node_size=10, edge_color="gray", width=0.5)
  1219. plt.title("Neuron Connectivity Graph")
  1220. # Convert to Base64
  1221. img_buffer = io.BytesIO()
  1222. plt.savefig(img_buffer, format="png")
  1223. img_buffer.seek(0)
  1224. img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")
  1225. plt.close() # Free memory
  1226. return img_base64
  1227. except Exception as e:
  1228. return str(e)
  1229. @csrf_exempt
  1230. def get_activity_data_by_file_name(request):
  1231. try:
  1232. unique_directory = request.GET.get('dir')
  1233. page = int(request.GET.get('page', 1))
  1234. items_per_page = int(request.GET.get('items_per_page', 300))
  1235. if not unique_directory:
  1236. return JsonResponse({'error': 'Missing directory identifier'}, status=400)
  1237. json_directory = os.path.join(settings.MEDIA_ROOT, 'csv', unique_directory)
  1238. final_json_file_path = os.path.join(json_directory, 'activity_data.json')
  1239. if not os.path.exists(final_json_file_path):
  1240. return JsonResponse({'error': 'JSON file not found'}, status=404)
  1241. with open(final_json_file_path, 'r') as f:
  1242. data_dict = ujson.load(f)
  1243. # Paginate the data dictionary
  1244. paginator = Paginator(list(data_dict.items()), items_per_page)
  1245. paginated_data = paginator.get_page(page)
  1246. paginated_data_dict = {key: value for key, value in paginated_data}
  1247. return JsonResponse(paginated_data_dict, safe=False)
  1248. except Exception as e:
  1249. return JsonResponse({'error': str(e)}, status=500)
  1250. # Cache the JSON data in memory
  1251. @lru_cache(maxsize=1)
  1252. def load_gids_activity_indexes(cache_file_path):
  1253. with open(cache_file_path, 'r') as f:
  1254. return ujson.load(f)
  1255. def extract_ids_from_json(json_file_path):
  1256. """Extract IDs from the JSON file."""
  1257. Gids_array = []
  1258. # Read and process the JSON file
  1259. try:
  1260. with open(json_file_path, 'r') as json_file:
  1261. for line in json_file:
  1262. try:
  1263. json_data = json.loads(line)
  1264. if isinstance(json_data, list):
  1265. for node in json_data:
  1266. Gids_array.append(int(node['id']))
  1267. except (KeyError, ValueError, json.JSONDecodeError) as e:
  1268. print(f"Error processing JSON line: {line.strip()} - {e}")
  1269. except FileNotFoundError:
  1270. print(f"File not found: {json_file_path}")
  1271. except Exception as e:
  1272. print(f"An error occurred: {e}")
  1273. return array.array('i', Gids_array)
  1274. @csrf_exempt
  1275. def LoadActivityData(request):
  1276. try:
  1277. if request.method == 'POST':
  1278. Gids_array = []
  1279. gids_activity_indexes = {}
  1280. number_of_spikes = 0
  1281. file_id = request.GET.get('dir')
  1282. if not file_id:
  1283. return JsonResponse({'error': 'Missing file ID'}, status=400)
  1284. json_directory = os.path.join(settings.MEDIA_ROOT, 'hip_form', "activities")
  1285. if file_id == '1':
  1286. final_csv_file_path = os.path.join(json_directory, 'Activity_CA3.csv')
  1287. elif file_id == '2':
  1288. final_csv_file_path = os.path.join(json_directory, 'Activity_MEC.csv')
  1289. else:
  1290. return JsonResponse({'error': 'Invalid file ID'}, status=400)
  1291. # Handle GIDs if they are present
  1292. gIDs_raw = request.POST.get('gIDs')
  1293. if gIDs_raw:
  1294. try:
  1295. Gids_list = json.loads(gIDs_raw)
  1296. except json.JSONDecodeError:
  1297. Gids_list = gIDs_raw.split(',')
  1298. try:
  1299. Gids_array = array.array('i', map(lambda x: int(float(x)), Gids_list))
  1300. except ValueError as e:
  1301. return JsonResponse({'error': f'Invalid GIDs data: {e}'}, status=400)
  1302. data = parse_activity_csv(final_csv_file_path)
  1303. keys = sorted(data.keys(), key=float)
  1304. gids_activity_indexes = get_activity_indexes(data, Gids_array)
  1305. number_of_spikes = count_csv_lines(final_csv_file_path)
  1306. response_data = {
  1307. 'data': data,
  1308. 'keys': keys,
  1309. 'gids_activity_indexes': gids_activity_indexes,
  1310. 'number_of_spikes': number_of_spikes,
  1311. }
  1312. return JsonResponse(response_data, safe=False)
  1313. return JsonResponse({'error': 'Invalid HTTP method'}, status=405)
  1314. except Exception as e:
  1315. print("Error in LoadActivityData:", e)
  1316. return JsonResponse({'error': str(e)}, status=500)
  1317. def parse_activity_csv(filepath):
  1318. activity_data = defaultdict(list)
  1319. with open(filepath, newline='') as csvfile:
  1320. reader = csv.reader(csvfile)
  1321. header = next(reader) # Skip header, if present
  1322. for row in reader:
  1323. gid = int(row[0])
  1324. time = float(row[1])
  1325. activity_data[str(time)].append(gid)
  1326. return dict(activity_data)
  1327. @csrf_exempt
  1328. def LoadAllLayers(request):
  1329. try:
  1330. json_directory = os.path.join(settings.MEDIA_ROOT, "hip_form")
  1331. data_list = []
  1332. total_size = 0
  1333. # connections = {}
  1334. # Iterate over each JSON file in the directory
  1335. for filename in sorted(os.listdir(json_directory)):
  1336. if filename.endswith(".json"):
  1337. file_path = os.path.join(json_directory, filename)
  1338. filename_without_ext = os.path.splitext(filename)[0]
  1339. try:
  1340. with open(file_path, 'r') as f:
  1341. file_data = ujson.load(f)
  1342. total_excitatory = sum(1 for neuro in file_data if neuro.get('type') == 1) # Count occurrences of 1 in the 'type' column
  1343. total_inhibitory = sum(1 for neuro in file_data if neuro.get('type') != 1) # Count occurrences of 0 in the 'type' column
  1344. total_neuros = len(file_data) # Count total neurons
  1345. data_list.append({
  1346. 'filename': filename_without_ext,
  1347. 'fileData': file_data,
  1348. 'total_neuros': total_neuros,
  1349. 'total_excitatory': total_excitatory,
  1350. 'total_inhibitory': total_inhibitory,
  1351. # 'connections':connections
  1352. })
  1353. total_size += os.path.getsize(file_path)/ (1024 * 1024) # Calculate total size in MB
  1354. except json.JSONDecodeError as e:
  1355. print(f"Error decoding JSON from file {file_path}: {e}")
  1356. except Exception as e:
  1357. print(f"Error reading file {file_path}: {e}")
  1358. # response_data = {'files': "", 'total_size': total_size, 'connections': connections}
  1359. response_data = {'files': data_list, 'total_size': total_size}
  1360. return JsonResponse(response_data, safe=False)
  1361. except requests.RequestException as e:
  1362. print(f"Request error: {e}")
  1363. return JsonResponse({'error': 'Failed to fetch data from EBRAINS'}, status=500)
  1364. except Exception as e:
  1365. print(f"An error occurred: {e}")
  1366. return JsonResponse({'error': 'An unexpected error occurred'}, status=500)
  1367. def count_csv_lines(file_path):
  1368. with open(file_path, 'r') as f:
  1369. return sum(1 for line in f)
  1370. @lru_cache(maxsize=1)
  1371. def compute_connections():
  1372. json_directory = os.path.join(settings.MEDIA_ROOT, "hip_form")
  1373. os.makedirs(json_directory, exist_ok=True)
  1374. json_path = os.path.join(json_directory, "connections.json")
  1375. ca3_file_paths = [
  1376. os.path.join(json_directory, f"CA3-{layer}.json") for layer in ["sl", "slm", "so", "sp", "sr"]
  1377. ]
  1378. ca1_file_paths = [
  1379. os.path.join(json_directory, f"CA1-{layer}.json") for layer in ["slm", "so", "sp", "sr"]
  1380. ]
  1381. ca3_neurons = pd.DataFrame(load_neurons_from_files(ca3_file_paths))
  1382. ca1_neurons = pd.DataFrame(load_neurons_from_files(ca1_file_paths))
  1383. # Select pyramidal cells (robust in case ntype codes differ or missing)
  1384. ca3_pyramidal = ca3_neurons[ca3_neurons['ntype'] == 19].copy()
  1385. ca1_pyramidal = ca1_neurons[ca1_neurons['ntype'] == 49].copy()
  1386. # Fallback if filtering empties the set
  1387. if ca3_pyramidal.empty:
  1388. print("No CA3 PCs after ntype==19 filter; using all CA3 neurons.")
  1389. ca3_pyramidal = ca3_neurons.copy()
  1390. if ca1_pyramidal.empty:
  1391. print("No CA1 PCs after ntype==49 filter; using all CA1 neurons.")
  1392. ca1_pyramidal = ca1_neurons.copy()
  1393. # Ensure coordinates are numeric and drop NaNs
  1394. for df_name, df in [("CA3", ca3_pyramidal), ("CA1", ca1_pyramidal)]:
  1395. before = len(df)
  1396. df[['x','y','z']] = df[['x','y','z']].apply(pd.to_numeric, errors='coerce')
  1397. df.dropna(subset=['x','y','z'], inplace=True)
  1398. after = len(df)
  1399. if after < before:
  1400. print(f"[WARN] Dropped {before-after} {df_name} rows with non-numeric coords.")
  1401. print(f"CA3 PCs: {len(ca3_pyramidal)}, CA1 PCs: {len(ca1_pyramidal)}")
  1402. # 1. Scale lengths from rat to mouse
  1403. SF_SR = calculate_scaling_factor(rat_volume=10.95, mouse_volume=2.83)
  1404. SF_SO = calculate_scaling_factor(rat_volume=6.47, mouse_volume=1.5)
  1405. # Apply the scaling factors to the original RAT lengths to get MOUSE lengths.
  1406. L_SR_mouse = 4465.3 * SF_SR # Previously used division, which was incorrect.
  1407. L_SO_mouse = 3795.59 * SF_SO # Scale the full SO length first...
  1408. L_SO_mouse_effective = L_SO_mouse / 2 # ...then apply the 50% adjustment for SO
  1409. L_total_effective_mouse = L_SR_mouse + L_SO_mouse_effective # 100% of SR + 50% of SO
  1410. # 2. Calculate total spines (CORRECTED STEP)
  1411. spine_density = 0.92 # spines/micrometer
  1412. # The total number of spines is the effective length multiplied by the density.
  1413. total_spines = L_total_effective_mouse * spine_density
  1414. # The 'inter_spine_distance' variable is not needed for the calculation and can be removed.
  1415. # 3. & 4. Calculate unique presynaptic partners.
  1416. avg_contacts_per_connection = 7.24 # From Hippocampome CA3-CA3 data
  1417. unique_ca3_partners_per_ca1 = total_spines / avg_contacts_per_connection
  1418. # 5. Calculate CA3 output divergence
  1419. CA1PC_census = 159341
  1420. CA3PC_census = 75376
  1421. # The number of CA1 cells a single CA3 cell targets is given by:
  1422. # (Partners per CA1) * (Total CA1 Population / Total CA3 Population)
  1423. avg_ca1_targets_per_ca3 = unique_ca3_partners_per_ca1 * (CA1PC_census / CA3PC_census)
  1424. # --- Print results for verification ---
  1425. print(f"Scaling Factor SR: {SF_SR:.4f}")
  1426. print(f"Scaling Factor SO: {SF_SO:.4f}")
  1427. print(f"Scaled Mouse Length SR: {L_SR_mouse:.1f} µm")
  1428. print(f"Scaled Mouse Length SO: {L_SO_mouse:.1f} µm")
  1429. print(f"Effective Length for CA3 Input: {L_total_effective_mouse:.1f} µm")
  1430. print(f"Total CA3 Synapses per CA1 PC: {total_spines:.0f}")
  1431. print(f"Avg. CA3 Partners per CA1 PC: {unique_ca3_partners_per_ca1:.0f}")
  1432. print(f"Avg. CA1 Targets per CA3 PC: {avg_ca1_targets_per_ca3:.0f}")
  1433. # --- Fixed 05% fan-out (≈54) and in-degree cap (510) ---
  1434. OUTDEG_TARGETS_PER_CA3 = max(1, int(round(avg_ca1_targets_per_ca3 * 0.05))) # ~54 from 1077
  1435. print(f"CA3 out-degree = {OUTDEG_TARGETS_PER_CA3} (05% of {avg_ca1_targets_per_ca3:.0f})")
  1436. connections = connect_gaussian_all(
  1437. ca3_df=ca3_pyramidal,
  1438. ca1_df=ca1_pyramidal, # or ca1_eligible if you keep the subset trick
  1439. avg_targets_per_ca3=OUTDEG_TARGETS_PER_CA3, # or your scaled fanout
  1440. d_max_um=None, # or a value if you want locality
  1441. sigma_factor=0.30, # your chosen σ scaling
  1442. ca1_in_degree_cap=unique_ca3_partners_per_ca1 # << hard cap per CA1
  1443. )
  1444. with open(json_path, 'w') as f:
  1445. json.dump(connections, f)
  1446. num_ca3 = len(connections)
  1447. nonempty = sum(1 for v in connections.values() if v)
  1448. avg_out = (sum(len(v) for v in connections.values()) / max(1, num_ca3))
  1449. print(f"CA3 with ≥1 target: {nonempty}/{num_ca3} | mean out-degree: {avg_out:.1f}")
  1450. # Check that all CA1 targets exist in the CA1 set
  1451. ca1_ids_set = set(ca1_pyramidal['gid'].astype(str))
  1452. missing = 0
  1453. for ca3_id, targets in connections.items():
  1454. for t in targets:
  1455. if str(t) not in ca1_ids_set:
  1456. missing += 1
  1457. if missing:
  1458. print(f"{missing} targets not found in CA1 pool (ID mismatch).")
  1459. return json_path
  1460. @csrf_exempt
  1461. def fetchConnections(request):
  1462. try:
  1463. #json_path = compute_connections()
  1464. json_path = os.path.join(settings.MEDIA_ROOT, "hip_form","connections", "connections.json")
  1465. # Load full data from file
  1466. with open(json_path, 'r') as f:
  1467. connections = json.load(f)
  1468. # Chunking logic
  1469. chunk_size = int(request.GET.get("chunk_size", 10000))
  1470. page = int(request.GET.get("page", 1))
  1471. keys = list(connections.keys())
  1472. total_pages = (len(keys) + chunk_size - 1) // chunk_size
  1473. start = (page - 1) * chunk_size
  1474. end = start + chunk_size
  1475. chunk = {k: connections[k] for k in keys[start:end]}
  1476. response_data = {
  1477. 'connections_chunk': chunk,
  1478. 'page': page,
  1479. 'has_more': end < len(keys),
  1480. 'total_pages': total_pages,
  1481. 'total_keys': len(keys)
  1482. }
  1483. return JsonResponse(response_data)
  1484. except Exception as e:
  1485. return JsonResponse({'error': str(e), 'connections': {}}, status=500)
  1486. def load_neurons_from_files(file_paths):
  1487. for file_path in file_paths:
  1488. yield from load_json_file_generator(file_path)
  1489. # Generator to load JSON files and yield one neuron at a time
  1490. def load_json_file_generator(file_path):
  1491. try:
  1492. with open(file_path, 'r') as file:
  1493. data = json.load(file)
  1494. for neuron in data:
  1495. yield {
  1496. 'gid': neuron.get('id'),
  1497. 'x': neuron.get('x'),
  1498. 'y': neuron.get('y'),
  1499. 'z': neuron.get('z'),
  1500. 'type': neuron.get('type'),
  1501. 'ntype': neuron.get('ntype')
  1502. }
  1503. except json.JSONDecodeError as e:
  1504. print(f"Error decoding JSON from {file_path}: {e}")
  1505. def calculate_scaling_factor(rat_volume,mouse_volume):
  1506. scaling_factor = (mouse_volume / rat_volume) ** (1/3)
  1507. return scaling_factor
  1508. def connect_all_pyramidal(ca3_neurons, ca1_neurons, average_CA1_contacted_by_CA3):
  1509. connections = {}
  1510. try:
  1511. # Process region-based connections (Dorsal/Ventral, Proximal/Distal)
  1512. # Dorsal-Dorsal connections
  1513. ca3_dorsal = ca3_neurons[ca3_neurons['dorsal_ventral'] == 'dorsal']
  1514. ca1_dorsal = ca1_neurons[ca1_neurons['dorsal_ventral'] == 'dorsal']
  1515. connections.update(connect_regions_regionally(ca3_dorsal, ca1_dorsal, average_CA1_contacted_by_CA3))
  1516. # Ventral-Ventral connections
  1517. ca3_ventral = ca3_neurons[ca3_neurons['dorsal_ventral'] == 'ventral']
  1518. ca1_ventral = ca1_neurons[ca1_neurons['dorsal_ventral'] == 'ventral']
  1519. connections.update(connect_regions_regionally(ca3_ventral, ca1_ventral, average_CA1_contacted_by_CA3))
  1520. # Proximal-Proximal connections
  1521. ca3_proximal = ca3_neurons[ca3_neurons['prox_dist'] == 'proximal']
  1522. ca1_proximal = ca1_neurons[ca1_neurons['prox_dist'] == 'proximal']
  1523. connections.update(connect_regions_regionally(ca3_proximal, ca1_proximal, average_CA1_contacted_by_CA3))
  1524. # Distal-Distal connections
  1525. ca3_distal = ca3_neurons[ca3_neurons['prox_dist'] == 'distal']
  1526. ca1_distal = ca1_neurons[ca1_neurons['prox_dist'] == 'distal']
  1527. connections.update(connect_regions_regionally(ca3_distal, ca1_distal, average_CA1_contacted_by_CA3))
  1528. except Exception as e:
  1529. print(f"Error in connect_all_pyramidal: {e}")
  1530. return connections
  1531. def compute_sigma(ca1_subset, factor=0.3):
  1532. bbox_size = np.max(ca1_subset[['x', 'y', 'z']].values, axis=0) - np.min(ca1_subset[['x', 'y', 'z']].values, axis=0)
  1533. avg_extent = np.mean(bbox_size)
  1534. return factor * avg_extent # Adaptive σ
  1535. def connect_regions_regionally(ca3_subset, ca1_subset, average_CA1_contacted_by_CA3):
  1536. # Connects neurons within the same anatomical region using Gaussian-weighted sampling.
  1537. try:
  1538. connections = {}
  1539. sigma = compute_sigma(ca1_subset) # Dynamic sigma
  1540. d_max = 500 # Optional: maximum radius in micrometers
  1541. for _, ca3_neuron in ca3_subset.iterrows():
  1542. connected_ids = safe_gaussian_connection(
  1543. ca3_neuron, ca1_subset,
  1544. num_targets=int(average_CA1_contacted_by_CA3),
  1545. sigma=sigma,
  1546. d_max=d_max
  1547. )
  1548. connections[ca3_neuron['gid']] = connected_ids
  1549. return connections
  1550. except Exception as e:
  1551. print(f"Error in connect_regions_regionally: {e}")
  1552. return {}
  1553. def safe_gaussian_connection(ca3_neuron, ca1_subset, num_targets=10, sigma=150, d_max=None):
  1554. # Attempts probabilistic sampling; falls back to nearest-neighbors if necessary.
  1555. ca3_xyz = ca3_neuron[['x', 'y', 'z']].values.astype(float)
  1556. ca1_xyz = ca1_subset[['x', 'y', 'z']].values.astype(float)
  1557. distances = np.linalg.norm(ca1_xyz - ca3_xyz, axis=1)
  1558. # Optional cutoff to avoid distant targets
  1559. if d_max is not None:
  1560. mask = distances <= d_max
  1561. if not np.any(mask):
  1562. return []
  1563. ca1_xyz = ca1_xyz[mask]
  1564. distances = distances[mask]
  1565. ca1_subset = ca1_subset.iloc[mask]
  1566. # Compute Gaussian weights
  1567. weights = np.exp(- (distances ** 2) / (2 * sigma ** 2))
  1568. probs = weights / np.sum(weights)
  1569. try:
  1570. selected_indices = np.random.choice(
  1571. ca1_subset.index,
  1572. size=min(num_targets, len(ca1_subset)),
  1573. replace=False,
  1574. p=probs
  1575. )
  1576. return ca1_subset.loc[selected_indices, 'gid'].tolist()
  1577. except Exception as e:
  1578. # Fallback to nearest neighbors
  1579. print(f"[Fallback] Gaussian sampling failed: {e}")
  1580. ca1_subset = ca1_subset.copy()
  1581. ca1_subset['distance'] = distances
  1582. nearest = ca1_subset.nsmallest(num_targets, 'distance')
  1583. return nearest['gid'].tolist()
  1584. def adaptive_sigma_global(ca1_df: pd.DataFrame, factor: float = 0.15) -> float:
  1585. """
  1586. Compute a single Gaussian σ (µm) from the spatial extent of the whole CA1 pool.
  1587. Smaller factor -> tighter locality; larger -> broader spread.
  1588. """
  1589. if ca1_df.empty:
  1590. return 100.0
  1591. bbox = ca1_df[['x', 'y', 'z']].agg(['min', 'max'])
  1592. span = (bbox.loc['max'] - bbox.loc['min']).to_numpy()
  1593. return float(factor * np.mean(span))
  1594. def connect_gaussian_all(ca3_df: pd.DataFrame,
  1595. ca1_df: pd.DataFrame,
  1596. avg_targets_per_ca3: float,
  1597. d_max_um: int = 300,
  1598. sigma_factor: float = 0.15,
  1599. ca1_in_degree_cap: int = 510,
  1600. rng_seed: int = 42) -> dict:
  1601. """
  1602. Domain-free CA3→CA1 connectivity with *per-CA1 in-degree cap*.
  1603. - For each CA3 PC, we sample ~avg_targets_per_ca3 CA1 PCs
  1604. using Gaussian distance weights.
  1605. - A CA1 PC will never receive inputs from > ca1_in_degree_cap CA3 PCs.
  1606. """
  1607. connections = {}
  1608. if ca1_df.empty or ca3_df.empty:
  1609. return connections
  1610. rng = np.random.default_rng(rng_seed)
  1611. # Ensure string gids
  1612. ca3_df = ca3_df.copy()
  1613. ca3_df['gid'] = ca3_df['gid'].astype(str)
  1614. ca1_df = ca1_df.copy()
  1615. ca1_df['gid'] = ca1_df['gid'].astype(str)
  1616. # Precompute one global σ from CA1 spatial extent
  1617. sigma_um = adaptive_sigma_global(ca1_df, factor=sigma_factor)
  1618. # In-degree tracker for CA1 targets
  1619. ca1_gids = ca1_df['gid'].to_numpy()
  1620. indeg = pd.Series(0, index=ca1_gids, dtype=int)
  1621. # For fairness, randomize CA3 order slightly
  1622. ca3_order = rng.permutation(ca3_df.index.values)
  1623. k = int(max(1, round(avg_targets_per_ca3)))
  1624. # Preload CA1 coords for speed
  1625. ca1_xyz_all = ca1_df[['x', 'y', 'z']].to_numpy(dtype=float)
  1626. for idx in ca3_order:
  1627. row = ca3_df.loc[idx]
  1628. q = row[['x','y','z']].to_numpy(dtype=float)
  1629. # Eligible CA1 = under cap (and optionally within d_max_um)
  1630. under_cap_mask = (indeg[ca1_gids].to_numpy() < ca1_in_degree_cap)
  1631. if not np.any(under_cap_mask):
  1632. # everyone hit the cap; this CA3 gets no targets
  1633. connections[row['gid']] = []
  1634. continue
  1635. ca1_xyz = ca1_xyz_all[under_cap_mask]
  1636. ca1_gids_elig = ca1_gids[under_cap_mask]
  1637. # Distances
  1638. dists = np.linalg.norm(ca1_xyz - q, axis=1)
  1639. # Optional hard radius
  1640. if d_max_um is not None:
  1641. rad_mask = (dists <= d_max_um)
  1642. if not np.any(rad_mask):
  1643. connections[row['gid']] = []
  1644. continue
  1645. dists = dists[rad_mask]
  1646. ca1_gids_elig = ca1_gids_elig[rad_mask]
  1647. # Gaussian weights
  1648. w = np.exp(-(dists ** 2) / (2 * sigma_um ** 2))
  1649. wsum = w.sum()
  1650. if wsum <= 0:
  1651. # fallback: uniform among eligible
  1652. probs = np.ones_like(w) / len(w)
  1653. else:
  1654. probs = w / wsum
  1655. k_eff = int(min(k, len(ca1_gids_elig)))
  1656. if k_eff <= 0:
  1657. connections[row['gid']] = []
  1658. continue
  1659. sel_idx = rng.choice(len(ca1_gids_elig), size=k_eff, replace=False, p=probs)
  1660. chosen = ca1_gids_elig[sel_idx].tolist()
  1661. # Update in-degree counts immediately
  1662. indeg.loc[chosen] += 1
  1663. # Save connections (use bare ints where possible)
  1664. connections[row['gid']] = [_to_numeric_id(t) for t in chosen]
  1665. # QA: check cap compliance
  1666. max_in = int(indeg.max()) if len(indeg) else 0
  1667. print(f"[QA] Max CA1 in-degree observed = {max_in} (cap={ca1_in_degree_cap})")
  1668. if max_in > ca1_in_degree_cap:
  1669. print("[WARN] In-degree cap violated; check logic.")
  1670. return connections
  1671. def _to_numeric_id(v):
  1672. try:
  1673. s = str(v)
  1674. return int(s) if s.isdigit() else s
  1675. except Exception:
  1676. return str(v)
  1677. def process_uploaded_file(file_path):
  1678. # Determine paths and filenames
  1679. json_directory = os.path.dirname(file_path)
  1680. original_file_name = os.path.basename(file_path)
  1681. file_name_without_extension = os.path.splitext(original_file_name)[0]
  1682. json_path = os.path.join(json_directory, f"{file_name_without_extension}.json")
  1683. fieldnames = ['id', 'x', 'y', 'z','type']
  1684. # Read the CSV and write to JSON
  1685. with open(file_path, 'r') as csv_file, open(json_path, 'w') as json_file:
  1686. json_data = []
  1687. csv_reader = csv.DictReader(csv_file, fieldnames=fieldnames)
  1688. for row in csv_reader:
  1689. # Convert row values and handle missing or invalid data
  1690. json_data.append({
  1691. 'id': int(row['id']),
  1692. 'x': float(row['x']),
  1693. 'y': float(row['y']),
  1694. 'z': float(row['z']),
  1695. 'type': int(row['type'])
  1696. })
  1697. # Write to JSON every 1000 records to manage memory usage
  1698. if len(json_data) >= 1000:
  1699. ujson.dump(json_data, json_file)
  1700. json_file.write('\n')
  1701. json_data = []
  1702. # Write any remaining data
  1703. if json_data:
  1704. ujson.dump(json_data, json_file)
  1705. json_file.write('\n')
  1706. # Delete the original CSV file after processing
  1707. try:
  1708. os.remove(file_path)
  1709. except OSError as e:
  1710. print(f"Error deleting file {file_path}: {e}")
  1711. def get_activity_indexes_for_SimulationData(activity_file, Gids):
  1712. # Create a dictionary for quick lookup
  1713. gids_dict = {int(float(id_)): index for index, id_ in enumerate(Gids)}
  1714. try:
  1715. with open(activity_file, 'r') as file:
  1716. reader = csv.reader(file)
  1717. # Sort rows based on the second column (index 1) and filter valid rows
  1718. sorted_rows = sorted(
  1719. (row for row in reader if row and len(row) > 1), # Filter empty or malformed rows
  1720. key=lambda row: float(row[1])
  1721. )
  1722. # Use a generator expression to build the result
  1723. gids_activity_indexes = [
  1724. gids_dict[int(row[0])] # Map activity_id to its index
  1725. for row in sorted_rows
  1726. if int(row[0]) in gids_dict # Only include valid activity IDs
  1727. ]
  1728. except ValueError as e:
  1729. # Handle cases where the ID cannot be converted to int
  1730. print(f"Skipping row due to ValueError: {e}")
  1731. return {'error': f'ValueError: {e}'}
  1732. except csv.Error as e:
  1733. return {'error': f'CSV error: {e}'}
  1734. return gids_activity_indexes
  1735. def get_activity_indexes(data_dict, Gids):
  1736. """ Returns a dictionary mapping GIDs from spike activity to their index in the Gids list. """
  1737. # Create a lookup for Gids: {gid: index}
  1738. gids_dict = {int(float(id_)): index for index, id_ in enumerate(Gids)}
  1739. # Create the mapping for all GIDs found in activity data
  1740. activity_index_map = {}
  1741. for t, activity_ids in data_dict.items():
  1742. for activity_id in activity_ids:
  1743. try:
  1744. gid = int(activity_id)
  1745. if gid in gids_dict:
  1746. activity_index_map[gid] = gids_dict[gid]
  1747. except ValueError as e:
  1748. print(f"Skipping invalid activity_id '{activity_id}': {e}")
  1749. return activity_index_map
  1750. @csrf_exempt
  1751. def LoadHumanHipForm(request):
  1752. try:
  1753. json_directory = os.path.join(settings.MEDIA_ROOT, "hip_form", "human")
  1754. final_json_file_path = os.path.join(json_directory, 'network.json')
  1755. with open(final_json_file_path, 'r') as f:
  1756. networkdata = ujson.load(f)
  1757. total_neurons = len(networkdata)
  1758. total_size = os.path.getsize(final_json_file_path) / (1024 * 1024) # in MB
  1759. mode = request.GET.get("mode", "full")
  1760. if mode == "half":
  1761. networkdata = networkdata[:total_neurons // 2]
  1762. return JsonResponse({
  1763. 'fileData': networkdata,
  1764. 'total_neurons': len(networkdata),
  1765. 'total_size': total_size
  1766. })
  1767. except ujson.JSONDecodeError as e:
  1768. print(f"Error decoding JSON from file {final_json_file_path}: {e}")
  1769. except Exception as e:
  1770. print(f"Error reading file {final_json_file_path}: {e}")
  1771. return JsonResponse({'error': str(e)}, status=500)
  1772. @csrf_exempt
  1773. def LoadHumanActivityData(request):
  1774. try:
  1775. if request.method == 'POST':
  1776. gids_activity_indexes = []
  1777. number_of_spikes = 0
  1778. # Get the file ID from the request
  1779. file_id = request.GET.get('dir')
  1780. if not file_id:
  1781. return JsonResponse({'error': 'Missing file ID'}, status=400)
  1782. # Determine the file path based on the selected value
  1783. json_directory = os.path.join(settings.MEDIA_ROOT, 'hip_form', "human")
  1784. if file_id == '1':
  1785. final_json_file_path = os.path.join(json_directory, 'activity.json')
  1786. final_csv_file_path = os.path.join(json_directory, 'activity.csv')
  1787. final_json_file_path_gids = os.path.join(json_directory, 'gids_activity_indexes.json')
  1788. # elif file_id == '2':
  1789. # final_json_file_path = os.path.join(json_directory, 'Activity_MEC.json')
  1790. # final_csv_file_path = os.path.join(json_directory, 'Activity_MEC.csv')
  1791. else:
  1792. return JsonResponse({'error': 'Invalid file ID'}, status=400)
  1793. # Read the JSON file and prepare the response data
  1794. with open(final_json_file_path_gids, "r") as file:
  1795. gids_activity_indexes = ujson.load(file)
  1796. # gids_activity_indexes = get_activity_indexes_for_SimulationData(final_csv_file_path, Gids_array)
  1797. number_of_spikes = count_csv_lines(final_csv_file_path)
  1798. # Read the JSON file and prepare the response data
  1799. with open(final_json_file_path, "r") as file:
  1800. data = ujson.load(file)
  1801. keys = sorted(data.keys(), key=float) # Sort keys as floats
  1802. # Return the data and keys in a JSON response
  1803. response_data = {
  1804. 'data': data,
  1805. 'keys': keys,
  1806. 'gids_activity_indexes': gids_activity_indexes,
  1807. 'number_of_spikes': number_of_spikes,
  1808. }
  1809. return JsonResponse(response_data, safe=False)
  1810. else:
  1811. return JsonResponse({'error': 'Invalid HTTP method'}, status=405)
  1812. except Exception as e:
  1813. print("Error in LoadActivityData:", e) # Debugging
  1814. return JsonResponse({'error': str(e)}, status=500)
  1815. def list_connection_types(request):
  1816. file_path = os.path.join(settings.MEDIA_ROOT,"connections_inh.hdf5")
  1817. if not os.path.exists(file_path):
  1818. return JsonResponse({"error": "HDF5 file not found."}, status=404)
  1819. with h5py.File(file_path, "r") as f:
  1820. connection_types = list(f.keys())
  1821. return JsonResponse({"types": connection_types})
  1822. def get_connection_data(request):
  1823. conn_type = request.GET.get("type")
  1824. if not conn_type:
  1825. return JsonResponse({"error": "Missing connection type."}, status=400)
  1826. file_path = os.path.join(settings.MEDIA_ROOT,"connections_inh.hdf5")
  1827. if not os.path.exists(file_path):
  1828. return JsonResponse({"error": "HDF5 file not found."}, status=404)
  1829. with h5py.File(file_path, "r") as f:
  1830. if conn_type not in f:
  1831. return JsonResponse({"error": f"Connection type '{conn_type}' not found."}, status=404)
  1832. dataset = f[conn_type][()]
  1833. connections = dataset.tolist()
  1834. return JsonResponse({"connections": connections})

views.py at commit 43a9c4d, no license · at the source

Overview

Authors: Maqsood Ali1,2, Roberto Smiriglia2, Emiliano Spera2, Safeer A. Mirani3, Sergio M.G. Solinas3, Michele Migliore2, Giorgio A. Ascoli4, Luca L. Bologna2
ORCID iDs: Maqsood Ali
  1. School of Advanced Studies, Center for Neuroscience, University of Camerino,Camerino, Italy
  2. Institute of Biophysics, National Research Council,Palermo, Italy
  3. Department of Life Sciences and Biotechnologies, University of Sassari (UNISS),Sassari, Italy
  4. Bioengineering Department, Center for Neural Informatics, Structure, and Plasticity, Krasnow Institute for Advanced Study, George Mason University,Fairfax, Virginia 22030 USA
Journal: Scientific reports, volume 16, issue 1, article 14345
Dates: received 23 December 2025; accepted 12 March 2026; published online 20 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41598-026-44588-0 · PMID 41862535 · PMCID PMC13144725 · OpenAlex W7139951715
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: computational modeling (no new data) (modality), mouse (organism)
Keywords: Computational biology and bioinformatics, Engineering, Mathematics and computing, Neuroscience
MeSH: Computer Simulation*, Models, Neurological*, Nerve Net*, Neurons*, Software*, Animals, Internet, Mice (* major topic)
Topic: Advanced Memory and Neural Computing (Electrical and Electronic Engineering, Engineering), according to OpenAlex
Funding: NextGenerationEU (IR0000011 (EBRAINS-Italy)); Ministero dell'Università e della Ricerca (2022JE5SK2 (FeNeL), 20228RMXBE (HIPPOCOMP)); HORIZON EUROPE Framework Programme (101147319 (EBRAINS2.0)); national institutes of health (R0139600)
Citations: cited by 1 paper (Europe PMC); 81 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

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

modeldb:267531

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: the end of the paper
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
  • 30 September 2026: the link answers (HTTP 200)
At the source: modeldb.science/267531

cnr-ibf-pa/NeuralCircuitVisualizer

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 43a9c4dbe38f72eca0bd35776d45dc00c6ecdc1e, 20 February 2026
Languages: Python (25), Shell (2), JavaScript (1)
Size: 90 files, 28 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (requirements.txt), continuous integration, documentation
Not found: license file, CITATION.cff, tests
Tools: h5py (1 file), Matplotlib (1 file), NetworkX (1 file), NumPy (1 file), pandas (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
29 files

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41598-026-44588-0.

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;
  • 28 scripts, each with its path and the digest of its content;
  • 6 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

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

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • no repository, dataset or request procedure was recognized in it

Read it in the paper: doi.org/10.1038/s41598-026-44588-0.

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, 8 authors, 4 keywords, 8 MeSH terms, 4 funders, 75 references.

Cite

This paper

Ali, M., Smiriglia, R., Spera, E., Mirani, S. A., Solinas, S. M., Migliore, M., Ascoli, G. A., & Bologna, L. L. (2026). Visualization and simulation of full-scale point-neuron circuits via the Neural Circuit Visualizer web platform. Scientific reports, 16(1), 14345. https://doi.org/10.1038/s41598-026-44588-0

BibTeX

@article{ali2026visualization,
author = {Ali, Maqsood and Smiriglia, Roberto and Spera, Emiliano and Mirani, Safeer A. and Solinas, Sergio M.G. and Migliore, Michele and Ascoli, Giorgio A. and Bologna, Luca L.},
title = {{Visualization and simulation of full-scale point-neuron circuits via the Neural Circuit Visualizer web platform}},
journal = {Scientific reports},
year = {2026},
month = mar,
volume = {16},
number = {1},
pages = {14345},
publisher = {Nature Publishing Group},
issn = {2045-2322},
doi = {10.1038/s41598-026-44588-0},
url = {https://doi.org/10.1038/s41598-026-44588-0},
pmid = {41862535},
pmcid = {PMC13144725}
}

RIS

TY - JOUR
AU - Ali, Maqsood
AU - Smiriglia, Roberto
AU - Spera, Emiliano
AU - Mirani, Safeer A.
AU - Solinas, Sergio M.G.
AU - Migliore, Michele
AU - Ascoli, Giorgio A.
AU - Bologna, Luca L.
TI - Visualization and simulation of full-scale point-neuron circuits via the Neural Circuit Visualizer web platform
T2 - Scientific reports
J2 - Sci Rep
PY - 2026
DA - 2026/03/20
VL - 16
IS - 1
SP - 14345
SN - 2045-2322
PB - Nature Publishing Group
DO - 10.1038/s41598-026-44588-0
UR - https://doi.org/10.1038/s41598-026-44588-0
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41598-026-44588-0",
"type": "article-journal",
"title": "Visualization and simulation of full-scale point-neuron circuits via the Neural Circuit Visualizer web platform",
"container-title": "Scientific reports",
"author": [
{
"family": "Ali",
"given": "Maqsood"
},
{
"family": "Smiriglia",
"given": "Roberto"
},
{
"family": "Spera",
"given": "Emiliano"
},
{
"family": "Mirani",
"given": "Safeer A."
},
{
"family": "Solinas",
"given": "Sergio M.G."
},
{
"family": "Migliore",
"given": "Michele"
},
{
"family": "Ascoli",
"given": "Giorgio A."
},
{
"family": "Bologna",
"given": "Luca L."
}
],
"container-title-short": "Sci Rep",
"volume": "16",
"issue": "1",
"page": "14345",
"DOI": "10.1038/s41598-026-44588-0",
"PMID": "41862535",
"PMCID": "PMC13144725",
"ISSN": "2045-2322",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41598-026-44588-0",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
20
]
]
}
}

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.3389/fninf.2023.1271059 [code]
Online interoperable resources for building hippocampal neuron models via the Hippocampus Hub.
Journal: Frontiers in neuroinformatics
In common: Matplotlib, NumPy, computational modeling (no new data), 17 references
[2] doi:10.7554/elife.95135 [code]
The NeuroML ecosystem for standardized multi-scale modeling in neuroscience.
Journal: eLife
In common: NetworkX, h5py, pandas, 2 other tools, computational modeling (no new data), mouse, 11 references
[3] doi:10.7554/elife.69013 [code]
Combining hypothesis- and data-driven neuroscience modeling in FAIR workflows.
Journal: eLife
In common: NetworkX, h5py, pandas, 2 other tools, 10 references
[4] doi:10.1371/journal.pcbi.1010086 [code]
Connectivity concepts in neuronal network modeling.
Journal: PLoS computational biology
In common: pandas, Matplotlib, NumPy, computational modeling (no new data), 11 references
[5] doi:10.1038/s43588-023-00417-2
Full-scale scaffold model of the human hippocampus CA1 area.
Journal: Nature computational science
In common: 12 references
[6] doi:10.7554/elife.90597 [code]
Hippocampome.org 2.0 is a knowledge base enabling data-driven spiking neural network simulations of rodent hippocampal circuits.
Journal: eLife
In common: computational modeling (no new data), mouse, 10 references
[7] doi:10.1007/s10827-024-00881-3 [code]
Formation and retrieval of cell assemblies in a biologically realistic spiking neural network model of area CA3 in the mouse hippocampus.
Journal: Journal of computational neuroscience
In common: pandas, Matplotlib, NumPy, computational modeling (no new data), mouse, 8 references
[8] doi:10.1371/journal.pcbi.1010941 [code]
NeuroML-DB: Sharing and characterizing data-driven neuroscience models described in NeuroML.
Journal: PLoS computational biology
In common: pandas, Matplotlib, NumPy, 10 references
[9] doi:10.1093/cercor/bhaf295 [code]
Building on models-a perspective for computational neuroscience.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: NetworkX, pandas, Matplotlib, 1 other tool, computational modeling (no new data), 8 references
[10] doi:10.3389/fninf.2022.884046 [code]
Modernizing the NEURON Simulator for Sustainability, Portability, and Performance.
Journal: Frontiers in neuroinformatics
In common: h5py, pandas, Matplotlib, 1 other tool, computational modeling (no new data), 8 references

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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0.svg)](https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0.svg
   :target: https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0"><img src="https://openscicode.org/badge/doi/10.1038/s41598-026-44588-0.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

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