PR 54 Prose Coordinates - neurostuff/ns-pond-ingestion-workflow GitHub Wiki

#54 Coordinates in prose

Opened 2026-10-06. Frozen once merged. The system as it stands is described in Design.

Some papers report their peaks only in the text — "greater activation in the left amygdala (x = −22, y = −4, z = −18; t = 4.1)" — and many report a few there besides their tables: a correlation computed at a peak, a seed, an ROI follow-up. The pipeline read tables only. This change gives prose the same two steps tables have, an extraction and a model call, in three stages that run when prose_model is set.

download → extract → passages → metadata → triage → analyses → prose → resolve → space → upload → sync
           (tables)  (prose)               (tables)            (prose)

The stages, and why there

passages is prose's extraction, as extract is the tables': no model. It requires download, nothing later. A paper reporting its peaks only in its text often has no table that parsed, or none at all, so neither a successful extraction nor table triage is asked for. It reads the article's own download — best first, pubget's JATS, Elsevier's XML, ACE's HTML, a PDF's text layer — keeps the Methods and Results with every figure legend (the whole text when no section is recognised), leaves table markup out (the tables are the table path's), and keeps the passages the detector finds.

A cheap test on the raw file runs before anything is parsed. Tables and tags are stripped by regex and the detector is put to the text around every match of its patterns; a no is final. PDFs skip it, since their bytes are not text.

metadata runs after both extractions and before both models. It also takes an article with no extraction whose passages hold a coordinate — the providers are asked by identifier alone — and the prose model reads each passage with the article's title and abstract: the fine-tune was trained with them on every passage.

prose is the model call, one per passage. Its fingerprint includes the metadata it read with, so an article read before its metadata arrived is read again once it does. An article with no passage gets an empty result without a call, so resolve can pass its tables through.

resolve is a merge that costs nothing; kept apart from the model, its rule can be revised and re-run without a call. It reads prose and, beside it, analyses when the article has one, because requires names one parent. It writes the analyses payload plus one more collection, prose, so space reads it unchanged. An article with no extraction takes its space from the same Methods and Results.

Switching it on does not re-upload the corpus. Where the prose adds nothing, resolve records the analyses fingerprint it passed through and space keys its own on that, so space and upload stay fresh for every article already processed from its tables.

The only settings are prose_model and, when it has its own server, prose_api_base. The context the model reads, the passage cap and the sections kept were settled by the experiments below.

A file the detector cannot finish in a minute fails its article rather than holding its batch: one Elsevier file whose stripped tags left 500 spaces after a bare "x" held an overlapping-quantifier pattern for minutes before that was fixed.

Finding the passages

The detector began as the 2026-10-02 experiment's, and was measured on the 575 NeuroMetaBench gold peaks written in prose, in the text the stage reads.

recall of prose gold
experiment detector, extraction text 44.5%
loose triplets beside a cue, extraction text 72.9%
the same, download's Methods and Results 83.5%
lists in one bracket, abbreviated regions, "n.s." 92.2%

The loose pattern takes a triplet with no closing bracket after z — "(−8, −96, 2, Z = 9.15)", "(Left: −30, 22, −8, max z: 3.11 …)" — beside a coordinate cue: a statistic, "peak", a Brodmann area, an ROI. Reading the download rather than the extraction's text gained the peaks in figure legends and in Elsevier tables the text had flattened into paragraphs. The last row reads every item of a list in one bracket ("[−24, −96, 9; 42, −81, −6]", "(−30, −81, 30 and 36, −75, 33)"), stops "n.s." ending a sentence, counts MPFC or STS as anatomy, and keeps an uncued triplet in a sentence that reports others.

Of the 45 peaks still missed, 23 are in the download only inside a table, 11 outside the Methods and Results, and the rest are one-offs: "(2, y = 37, z = −8)", "(ROI-5, 63, −9, −6; …)", positive triplets with no coordinate word.

The detector refuses the shapes that recur and are not coordinates — consecutive numbers, citation-shaped brackets, thousands separators — and leaves the rest to the model.

Reading them

Base NuExtract3 with an instruction, an object-form template with a role per point, and constrained decoding. Test sets: 78 hand-labelled passages, 111 NeuroMetaBench passages with hand-checked labels, 300 synthetic passages built from the failures below.

Base NuExtract3, and a fine-tune of it, with an instruction, an object-form template with a role per point (result, roi, seed, target, prior_study, figure, other), and constrained decoding. Test sets: 78 hand-labelled passages, 111 NeuroMetaBench passages with hand-checked labels, 300 synthetic passages built from the failures the gold set showed.

Names. An analysis is named for the contrast, effect or model its coordinates are a result of — "patients < controls, covarying for IQ", "positive correlation with craving", "PPI with amygdala seed: threat > safe" — never a heading, method or region. The labels were redone to this convention for this change. Base NuExtract3 under the earlier prompt named analyses after section headings ("Whole-brain analyses"); judged by an LLM against the labels, names of result points:

gold exactly gold +partly NMB exactly NMB +partly
earlier prompt 14% 44% 30% 63%
naming prompt (N5) 31–33% 72–81% 50–59% 86–87%
fine-tune, same input 65% 93% 74% 91%

What moved the base model: naming rules (most of it), concrete forms rather than "A > B" placeholders (the model copied the placeholder), letting a peak sit under several contrasts, and worked examples. A section heading or the sentences around the passage add a few points; both together no more.

Constrained decoding was not enough on its own. The worked examples made the base model loop, repeating whole analyses until the token budget cut the JSON off. The schema now requires every key and caps analyses, points and name length just above the most any label holds, so a loop ends as valid JSON, and exact repeats are dropped after.

The fine-tune is v21's recipe unchanged — LoRA on attention, MLP and the linear-attention mixers, A_log, dt_bias, conv1d and the norms trained directly — on 5,875 passages (875 real: luna-labelled silver and NMB; 5,000 synthetic), each with a random choice of heading and surrounding context. Two epochs, 1.5 h on four 8 GB cards. Against the naming prompt on the same input:

coordinate precision grouping precision statistic type role
gold, prompt 77–81% 42–46% 93–94% 91%
gold, fine-tune 99% 81% 96% 92%
NMB, prompt 83–89% 76–83% 91% 92–93%
NMB, fine-tune 97% 97% 98% 90%

Recall is 96–98% for both. The prompt's lower precision is repeats: counted once per coordinate it is 96–98%. Role is where neither improves.

Passages without coordinates. 336 of the fine-tune's training passages (5.7%) have an empty answer. On 869 corpus passages luna found no coordinate in — the second round's training data, unseen by the first — the fine-tune answers nothing for 863 and gives a result point for 4: eight points, each made from numbers that are not a coordinate, "(BA 9, 47)" as (47, 47, 47), "t(14) = 2.96" as (2.96, 2.96, 2.96). A point must now be written in the passage as x, y and z in that order, at most two numbers apart, which drops seven of the eight and 3 of 14,265 labelled points — a PDF's "227" for −27, and two silver labels not in their passage.

End to end

The whole path — detector, model, resolve against the paper's own table analyses — over the 67 NeuroMetaBench papers whose prose passages are in the test split (the fine-tune trained on the other papers' passages):

gold peaks held statistic type / value on added points
tables alone 675 of 1,380 (48.9%)
tables and prose, base 794 (57.5%) 81.9% / 86.2%
tables and prose, fine-tune 796 (57.7%) 94.4% / 89.9%
the same, read from the download 797 (57.8%) 95.7% / 94.7%

Over all 154 papers, 222 of the 266 prose gold peaks no table holds are added; 25 are missed by the detector and 19 are read under a non-result role (an ROI or crosshair the paper defined from its own result) and so dropped. The models recover the same gold; they differ in what else they upload.

In the wild, on 150 random corpus articles holding a passage — none of them in any training set — every point resolve would upload, judged with its passage:

real results name right
base, articles with table analyses 76% 72%
base, articles without 93% 73%
fine-tune, with 98% 100%
fine-tune, without 99% 96%

28 of the 87 articles without any table analysis gain results.

A second round did not help. Adding 2,400 corpus passages labelled by luna (names unreviewed) and 1,500 synthetic passages for the first round's failures — "lower in A than B" read as A > B, a null reverse contrast taken as the name, "-6–8 22" — fixed those failures on their synthetic test and raised the statistic accuracy (98.8% / 97.5% end to end), but merged covariate-adjusted repeats into one analysis (gold grouping precision 81% → 68%) and slipped in the wild (95% real, 92–94% names). A third round with the synthetic passages but none of the corpus ones fixed the same failures and kept the statistics (97.7% / 96.6%), but lost grouping precision as well (gold 74%, NMB 91%) and was a little behind in the wild (96–99% real, 91–95% names) — so it is the synthetic set, most likely the passages teaching "one analysis, though shown separately per condition", that teaches the model to merge. The first round's model stays the recommendation.

Resolving against the tables

A prose result within 1 mm of one of the article's table peaks is usually the text restating it — but not always. Of 277 hand-labelled prose results on a table peak, a judge reading both called 85 a different analysis at that peak: a correlation, a conjunction, an ROI follow-up. Keeping a prose result when it names an analysis computed at a peak, and no table analysis there does, keeps 46 of those 85 and re-keeps 20 of the 192 restatements — 79% agreement. A coordinate the prose reports under two contrasts stays under both.

Results, ROIs, seeds and stimulation targets are uploaded; other studies' peaks, display locations and anything else are counted in the summary and dropped. Each prose analysis holds one role, recorded in its metadata — which upload sends with the analysis, since a Neurostore point has no field for it — and seed points are flagged is_seed. An ROI or seed at one of the article's own table peaks is that result reused, already uploaded from the table, and is not uploaded again.

The corpus, and the upload

Run on 2026-10-07 over every downloaded article, with the round-one fine-tune.

articles
downloaded 631,451
ruled out by the raw filter, before parsing 543,610 (86%)
with a passage 37,674 (78,691 passages)
gaining prose points after resolve 22,153
of those, never uploaded before 11,810 (69,610 points)
of those, already uploaded 10,343 (47,222 points)

Of 3,000 articles the filter ruled out, read anyway, five produced a passage and none of those was a coordinate (a voxel size, scan parameters, bacterial counts); it ruled out none of the 575 NeuroMetaBench gold prose peaks.

A new article gains a median of 4 coordinates (mean 5.9; 90% gain 13 or fewer) in a median of 2 analyses; an uploaded one 3 (mean 4.6). Of what resolve keeps, 86,677 points are results, 21,591 ROIs, 7,576 seeds and 1,029 targets; 26,310 more were restatements of a table peak and 7,005 other studies' peaks or display locations.

Luna judged every point resolve would upload from 150 new and 150 uploaded articles, none in any training set: 97.1% and 96.5% are real coordinates, names right for 90% and 89% (6–7% more partly); 149 and 148 of the articles hold at least one real point. Weakest: seeds in articles with no table, the role right for 72%.

The model read took four hours on four 8 GB cards. When the stages were split, passages was run over the corpus in the catalog (an hour; identical output) and the read migrated in as prose artifacts wherever the stage would have read the same thing — the same passages, and the metadata the model saw unchanged since — 35,948 articles. The 1,726 that had gained metadata since, or had a failed passage, were read again.

Uploaded to Neurostore the same day, as the nuextract-v21 version: 11,809 new articles and 10,343 existing ones gaining prose analyses — 115,570 analyses in all, tables included. One failed: a new base study whose author list is longer than Neurostore's authors index allows. Ten canary articles, then forty more at random, read back from the API match the catalog's analyses and points exactly; prose analyses carry source: prose and their role in their metadata. Two traps on the way: 314 of the existing articles had upload rows migrated with an empty fingerprint, which reads as fresh under any version, so the run needed --refresh upload; and the API serves a cached study for a while after an update, so read-back needs a cache-busting query.

Synced to the ns-pond tree the same day: 22,152 articles; pmids.tsv went from 39,249 to 51,415 rows with none lost (19 changed their source column to the PDF extraction triage now judges). 130 articles failed the first pass, having no extraction — 124 publisher pages ACE could not identify, which the generic reader behind passages had read — so passages now keeps the text it read, as extract does, and sync writes such an article from it: its download, its text, no tables. In stage1, which pondie reads, a prose analysis now carries source: prose and its role. 150 sampled articles match the catalog by content.

Reading extract's text instead of the download was measured and rejected: 75.5% of the gold prose peaks against 92.2%, mostly because extract writes each table into the text as flattened rows of numbers.

Meta-analyses stored as meta

A meta-analysis reports peaks pooled from other papers; counted beside group studies, it counts them twice. Neurostore's level says which an article is (group or meta), but upload set every article to group, and reset an existing base study to it. Upload now reads the title (services/study_level.py): a meta-analysis, systematic or umbrella review, meta-regression, or an ALE or SDM analysis is meta; a primary study that took its regions from one ("meta-analytically defined", "brain regions based on a meta-analysis in patients with T2DM: a resting-state fMRI study") is not; anything else keeps the level it has.

The same rule was applied to Neurostore on 2026-10-07: of 65,220 base studies, 1,890 are titled as meta-analyses, 83 of them already meta; 1,807 base studies and 3,898 study versions (every source: 1,351 nuextract-v21, 1,173 llm, 299 neurosynth, 227 neuroquery, …) were set to meta, the old levels saved beside the run. A version counted only when its title is its base study's with more after it; 14 versions titled as another paper's meta-analysis were left, as versions linked to the wrong base study.

Found, not fixed

Twenty-four points per analysis is a cap the model hits. The schema caps an analysis at 24 points, just above the largest label. 19 of 88,957 analyses, in 14 articles, end at exactly 24 — one passage of a twin study lists 71 triplets — so their remaining points and their own contrasts are lost. Splitting long passages before the model would fix it.

An author list too long for Neurostore's index fails the upload of a new base study (ix_base_studies_authors, 2,704 bytes); upload should truncate it.

"From a prior study" is a role, and should be a flag beside it: a paper can quote another study's result, or take another study's peak as its own ROI or seed. 12% of ROI and 7% of seed points come from other papers and are uploaded as this study's. Issue #55.

A seed or ROI is uploaded as an analysis like any other. Its role is in the analysis metadata only; a reader that ignores the metadata takes a seed for a peak.

The resolve rule is a heuristic, at 79% agreement with a judge. A model call does not beat it: given the passage, the prose analysis and the table analyses at the peak, base NuExtract3 agrees 71% (it keeps nearly every overlap) and the fine-tune 75%.

The fine-tuned weights are on beast only (/data/alejandro/jk-prose-coords/ft/merged-n5-final), not published.