Buckets:

illusion-brain/brain-domains / scripts /build_slice_catalog.py
redmingwei's picture
download
raw
10 kB
#!/usr/bin/env python3
"""build_slice_catalog — L3 检索层:把每个切片做成可检索记录
不改写数据、不生成任务样本,只做"能查"。核心不是多一个 parquet,而是:
**检索出来的每一行都带着该行的适用性限制**,否则查的人会把"不可用的字段"
当成可用。因此 catalog 里除数值外还有 caveats(逐行计算)与 tier(L0–L4 到位情况)。
两级粒度:
level=slice 来自 l2_invariants(成员 × 切片)
level=region 来自 l2_region_invariants(成员 × 切片 × 区,逐单元真标签)
输出:
framework/slice_catalog.parquet
framework/retrieval_index.json(字段表 + 词表 + caveat 规则 + 示例查询命中数)
"""
from __future__ import annotations
import json
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path("/domains")
OUT = ROOT / "framework"
# ---------------- 成员元数据(物种/模态/是否有逐单元区标签)
# unknown 表示本会话没有可靠来源,宁可留空也不猜
MEMBER_META = {
"steinmetz": ("mouse", "spikes", True),
"counts_v4": ("mouse+macaque", "counts", False),
"primate_macaque_nlb": ("macaque", "spikes", True),
"primate_dmfc_rsg": ("macaque", "spikes", False),
"primate_human_eeg": ("human", "eeg", False),
"omnibrain_multi": ("mouse+macaque+human", "spikes", False),
"omnibrain_spike_view": ("mouse+macaque+human", "spikes", False),
"omnibrain_spike_view_human": ("human", "spikes", False),
"omnibrain_spike_view_macaque": ("macaque", "spikes", False),
"omnibrain_eeg_view": ("human", "eeg", False),
"omnibrain_graph_view": ("mouse", "connectome", False),
"omnibrain4d_eeg_train": ("human", "eeg", False),
"omnibrain4d_eeg_test": ("human", "eeg", False),
"flywire": ("fly", "connectome", False),
}
# 逐行 caveat 规则:条件 + 说明
REGION_HIGH_DISPERSION = {"SCs", "ILA", "CA3", "PL", "MOs"}
def caveats_for(row) -> list[str]:
c = []
mem = str(row["member"])
modality = str(row["modality"])
if modality == "eeg":
c.append("EEG 的跨通道共同分量已被白化:rho1/rho_half 不适用,只能看单通道 ACF")
if mem == "omnibrain_multi" and "human" in str(row["species"]):
c.append("人类颅内部分无区标签(NWB 无 electrodes 表),region 只能为空")
if str(row["region_confidence"]) == "none":
c.append("无区标签")
if pd.notna(row.get("region")) and str(row["region"]) in REGION_HIGH_DISPERSION \
and mem == "steinmetz":
c.append("该区跨 session 离散度 >1.4(log 尺度),不要按区引用绝对值")
st = row.get("rho_half_status")
if pd.notna(st) and st != "ok":
c.append(f"rho_half 不可估({st}),只有上界/下界")
tt = row.get("tau_status")
if pd.notna(tt) and tt != "ok":
c.append(f"τ 不可用({tt})")
if pd.notna(row.get("n_units")) and float(row["n_units"]) < 8:
c.append("单元数 <8,任何区级量的方差都很大")
# 注意:PR 不能跨成员比较是**全局**限制,写在 retrieval_index.gaps 里,
# 不挂到每一行——否则每行都至少带一条 caveat,逐行警告会被淹没。
return c
# ---------------- 1. 切片级
L = pd.read_parquet(OUT / "l2_invariants.parquet")
keep = ["member", "slice", "n_bins", "bin_ms", "rate_hz", "rho1", "pooled_acf_lag1",
"rho_half_ms", "rho_half_status", "rho_zero_ms", "pr", "erank", "spec_slope",
"tau_ms", "tau_status", "acf1_half_ms", "acf1_half_status"]
keep = [k for k in keep if k in L.columns]
S = L[keep].copy()
S["level"] = "slice"
S["region"] = np.nan
S["region_confidence"] = "n/a"
S["n_units"] = np.nan
print(f"slice 级: {len(S)} 行")
# ---------------- 2. 区级
R = pd.read_parquet(OUT / "l2_region_invariants.parquet")
keep_r = ["member", "slice", "region", "n_units", "n_bins", "bin_ms", "rate_hz", "rho1",
"pooled_acf_lag1", "rho_half_ms", "rho_half_status", "rho_zero_ms", "pr",
"erank", "spec_slope", "label_source"]
keep_r = [k for k in keep_r if k in R.columns]
G = R[keep_r].copy()
G["level"] = "region"
_conf = {"allen_acronym": "anatomical", "dandi_000070_electrodes": "anatomical"}
G["region_confidence"] = G.label_source.map(lambda s: _conf.get(s, str(s)))
for col in ("tau_ms", "tau_status", "acf1_half_ms", "acf1_half_status"):
G[col] = np.nan
print(f"region 级: {len(G)} 行")
C = pd.concat([S, G], ignore_index=True, sort=False)
C["duration_s"] = C.n_bins.astype(float) * C.bin_ms.astype(float) / 1000.0
# ---------------- 3. 成员元数据 + 来源 pin
meta = C.member.map(lambda m: MEMBER_META.get(m, ("unknown", "unknown", None)))
C["species"] = [m[0] for m in meta]
C["modality"] = [m[1] for m in meta]
C["has_unit_region"] = [m[2] for m in meta]
SRC = pd.read_csv(OUT / "sources.csv")
PIN = dict(zip(SRC.source_id, SRC.pin_value))
STAT = dict(zip(SRC.source_id, SRC.pin_status))
MEM2SRC = {
"steinmetz": "steinmetz2019",
"primate_macaque_nlb": "dandi000070",
"primate_dmfc_rsg": "dandi000130",
"primate_human_eeg": "things-eeg2",
"omnibrain_eeg_view": "things-eeg2",
"omnibrain4d_eeg_train": "things-eeg2",
"omnibrain4d_eeg_test": "things-eeg2",
"omnibrain_multi": "dandi000950",
"flywire": "flywire",
}
C["source_id"] = C.member.map(MEM2SRC)
C["pin_value"] = C.source_id.map(PIN)
C["pin_status"] = C.source_id.map(STAT)
C["unit_id"] = (C.member.astype(str) + "|" + C.slice.astype(str) + "|"
+ C.region.fillna("-").astype(str))
# ---------------- 4. L0–L4 到位情况(每行)
C["tier_L0"] = C.pin_status.fillna("none")
C["tier_L1"] = "dedup_checked"
C["tier_L2"] = np.where(C.rho_half_status == "ok", "rho_half_ok",
"rho_half_unavailable")
C["tier_L3"] = "retrieval_only"
C["tier_L4"] = np.where(C.has_unit_region == True, "unit_region_verified",
"region_unverified")
C["caveats"] = [caveats_for(r) for _, r in C.iterrows()]
C["n_caveats"] = C.caveats.map(len)
# 数值列四舍五入,避免 inf/巨大的浮点尾巴
for c in ("rho_half_ms", "rho_zero_ms", "acf1_half_ms", "tau_ms", "rate_hz",
"rho1", "pooled_acf_lag1", "pr", "erank", "spec_slope", "duration_s"):
if c in C.columns:
C[c] = C[c].astype(float).round(6)
order = ["unit_id", "level", "member", "species", "modality", "region",
"region_confidence", "has_unit_region", "slice", "n_units", "n_bins", "bin_ms",
"duration_s", "rate_hz", "rho1", "pooled_acf_lag1", "rho_half_ms",
"rho_half_status", "rho_zero_ms", "pr", "erank", "spec_slope", "tau_ms",
"tau_status", "acf1_half_ms", "acf1_half_status", "source_id", "pin_value",
"pin_status", "tier_L0", "tier_L1", "tier_L2", "tier_L3", "tier_L4",
"n_caveats", "caveats"]
C[order].to_parquet(OUT / "slice_catalog.parquet", index=False)
print(f"catalog: {len(C)} 行, {C.unit_id.nunique()} 个唯一 unit_id")
print("species 分布:", C.drop_duplicates("member").species.value_counts().to_dict())
print("无元数据的成员:", sorted(set(C[C.species == "unknown"].member)))
# ---------------- 5. 示例查询(命中数写进 JSON,便于文档引用真实数字)
def q(label, **kw):
m = pd.Series(True, index=C.index)
for k, v in kw.items():
if isinstance(v, (list, tuple, set)):
m &= C[k].isin(list(v))
else:
m &= (C[k] == v)
sub = C[m]
print(f" {label:38s} -> {len(sub):4d} 行")
return int(len(sub))
print("\n示例查询命中数:")
ex = {
"mouse_CA1_region_level": q("小鼠 CA1(区级)", member="steinmetz", level="region",
region="CA1"),
"macaque_rho_half_ok": q("猕猴 rho_half 可估(区级)", member="primate_macaque_nlb",
level="region", rho_half_status="ok"),
"has_unit_region": q("有逐单元区标签的成员", has_unit_region=True),
"no_caveat": q("无任何 caveat", n_caveats=0),
}
ex["long_record_gt_1000s"] = int((C[C.level == "slice"].duration_s > 1000).sum())
print(f" {'时长 >1000 s':38s} -> {ex['long_record_gt_1000s']:4d} 行")
payload = {
"schema_version": "1.0",
"n_rows": int(len(C)),
"n_slice_level": int((C.level == "slice").sum()),
"n_region_level": int((C.level == "region").sum()),
"levels": ["slice", "region"],
"fields": {
"unit_id": "member|slice|region 的唯一键",
"level": "slice=成员×切片;region=成员×切片×区(逐单元真标签)",
"has_unit_region": "该成员是否有逐单元区标签(True 才能做区级分析)",
"rho_half_ms": "跨单元共享相关的半衰(模型无关);EEG 不适用",
"tau_status": "plateau 拟合的状态;非 ok 时 tau_ms 不可用",
"tier_L2": "该行 L2 是否可用(rho_half_ok / rho_half_unavailable)",
"tier_L4": "区身份是否逐单元核验过(unit_region_verified / region_unverified)",
"caveats": "该行的适用性限制,检索后必须先读",
},
"vocabularies": {
"species": sorted(set(C.species.dropna())),
"modality": sorted(set(C.modality.dropna())),
"member": sorted(set(C.member.astype(str))),
"region": sorted(set(C.region.dropna().astype(str))),
},
"gaps": {
"eeg_rho_unavailable": "EEG 成员的 rho1/rho_half 被白化,不可用",
"human_intracortical_no_region": "人类颅内无区标签(NWB 无 electrodes 表)",
"pr_not_comparable": "PR 不能跨成员比较",
"no_quality_model": "三条质量轴均为负结果,catalog 不含 quality 分",
},
"example_queries": ex,
}
(OUT / "retrieval_index.json").write_text(
json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
print("\n已写 slice_catalog.parquet 与 retrieval_index.json")
print("caveat 条数分布:", C.n_caveats.value_counts().sort_index().to_dict())

Xet Storage Details

Size:
10 kB
·
Xet hash:
f49b36dd78ab878c9a38b63337eed99be23fb00a9050ad63c0979414b58ceb77

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.