Active Phase 0 utilities:
get_ds003745.sh: clone/pin OpenNeuro ds003745 2.1.1 and selectively retrieve pilot files.run_fmriprep_ds003745.sh: fMRIPrep 25.2.5 single-subject wrapper,MNI152NLin6Asymonly.build_ds003745_runlist.py: build a deterministic cohort runlist from the pinned participants table, with explicit exclusions.run_fmriprep_ds003745_batch.sh: bounded-concurrency, resumable batch wrapper that skips complete outputs and stops on incomplete existing outputs.audit_fmriprep_ds003745.py: audit both Shared Reward runs, masks, confounds, and participant reports; optionally emit a retry manifest.convert_harmonized_events.py: source-preserving common full-trial event derivative for ds003745 or RF1.summarize_events.py: per-run timing/count QC.resample_to_rf1_grid.shandcheck_grid.py: identity-gridwsinc5BOLD/nearest-neighbor mask resampling and exact verification.build_resampling_manifest.py,run_resampling_batch.py, andaudit_resampling.py: deterministic run-level planning, bounded/restartable RF1-grid resampling, and independent cohort completeness QC.build_characterization_manifest.py,run_smoothness_batch.py, andaudit_smoothness.py: one frozen cross-dataset input contract, bounded/restartable AFNI baseline measurement, and a consolidated run-level audit table.build_target_smoothing_manifest.py,run_target_smoothing_batch.py, andaudit_target_smoothing.py: the analysis-ready 6-mm contract, bounded/restartable target smoothing, and independent geometry plus achieved-smoothness audit.smooth_with_feat_susan.sh,build_susan_comparison_manifest.py,run_susan_comparison.py, andaudit_susan_comparison.py: a non-production control reproducing FEAT's 6-mm SUSAN stage and measuring baseline, AFNI total-target, and SUSAN fixed-kernel outputs with the same AFNI estimator.plot_smoothness_comparison.py: the tracked mean ± run-level SEM comparison of baseline, AFNI-total-target, and FEAT-equivalent SUSAN smoothness.create_common_analysis_mask.py: nearest-neighbor resampling of the TemplateFlow MNI152NLin6Asym brain mask onto the exact RF1 reference grid.create_coverage_eligible_mask.py: provenance-tracked RF1-grid resampling of the historical cerebellum/brainstem exemption and construction of the fixed eligible coverage denominator.build_analysis_qc_manifest.py,run_analysis_qc_batch.py,audit_analysis_qc.py, andplot_analysis_qc.py: frozen/restartable post-smoothing tSNR, motion, fixed-mask coverage, review flags, subject summaries, and plots.build_event_qc_manifest.py,run_event_qc_batch.py, andaudit_event_qc.py: source-preserving full-trial conversion, condition counts, missed-trial exclusions, and run-to-subject usability aggregation.build_ratings_qc_manifest.pyandaudit_ratings_qc.py: explicit ratings-source resolution, six-cell validation, raw means/counts, provenance hashes, and subject-level ratings rules.build_analysis_cohort.py: strict inventory reconciliation and separate task-valid versus ratings-qualified L1/L2 manifests. It applies established missing-event, >25%-missed, and curated task exclusions while preserving usable opposite runs; zero-count modeled conditions become explicit review holds.build_fsl_confounds_manifest.py,generate_fsl_confounds.py,run_fsl_confounds_batch.py, andaudit_fsl_confounds.py: the single-echo ds003745 nuisance layer matching RF1's fMRIPrep base-column policy while explicitly omitting inapplicable TEDANA ICA regressors.generate_l1_evs.py: audited three-column EV generation from the harmonized full-trial derivatives.render_pooled_fsf.py,L1stats.sh, andrun_L1stats.sh: narrow transformation of the retained historical FSFs and bounded activation-followed-by-PPI execution.render_pooled_l2_fsf.py,L2stats.sh,run_L2stats.sh, andaudit_outputs.py: bounded fixed effects, explicit one-run passthrough, and L1/subject-level completeness auditing.measure_smoothness.sh,smooth_to_target.sh, andcompute_tsnr.py: thin wrappers around the explicitly configured authoritative RF1 implementations, preventing metric drift. tSNR uses the fixed common-mask/run-mask intersection and reports coverage against the fixed mask.harmonization_report.py: compact Phase 0 summary including the approved target status.run_logged.sh: local raw log plus a compact Git-trackable record for major Linux2 runs.
Default Temple roots live in project_config.sh and can be overridden explicitly. Large data remain outside Git. Use pilot subject lists, conservative fMRIPrep concurrency, and --dry-run before expensive processing.
The pinned OpenNeuro clone under sourcedata/ds003745 is an independent
DataLad dataset inside a parent-Git-ignored directory. It is intentionally not
registered as a Git submodule or DataLad subdataset of this analysis repository.
This keeps source acquisition reproducible without annexing analysis outputs or
requiring a large-file content sibling for the GitHub repository. A broader
datalad run/superdataset migration should be a separate, deliberate change.
On Linux2, create the dedicated Phase 0 Python/DataLad environment from
environment-phase0-linux2.yml, then run install_phase0_afni.sh to install
the complete official AFNI linux_ubuntu_24_64 command distribution into that
environment without editing shell dotfiles. This isolates DataLad, git-annex,
and AFNI from the base and FSL installations. The installer records the AFNI
archive URL and checksum. The fMRIPrep version remains independently pinned by
the Apptainer wrapper.
The Ubuntu 24 host must provide Motif's libXm.so.4 runtime. Install the
Ubuntu libxm4 package (from the Universe repository) before validating AFNI.
This is a host library required by AFNI's official Ubuntu 24 binaries, not a
Conda dependency.
For an active Phase 0 shell, prepend afni-bin and the environment bin
directory to PATH. Do not prepend the Conda environment's lib directory to
LD_LIBRARY_PATH: that can override the matching libmri.so shipped beside
the official AFNI binaries. The Ubuntu 24 build finds its bundled libraries via
its own runtime path.
Historical scripts/templates remain provenance only. The model-specific full-trial candidate is documented in templates/README.md; no pooled L3 is active.
Continuous BOLD is already normalized to MNI152NLin6Asym space. It is moved onto the exact RF1 grid by applying an identity transform with 3dAllineate -final wsinc5; this avoids introducing an additional spatial transform and reduces interpolation blur relative to cubic interpolation. Binary masks use nearest-neighbor 3dresample.
After the RF1 reference resource exists and the ds003745 fMRIPrep audit is complete, build the frozen run-level contract before launching any resampling:
python3 code/build_resampling_manifest.py \
--output logs/runlists/ds003745-resampling-ready.tsv \
--missing-output logs/runlists/ds003745-resampling-missing.tsvThe complete ds003745 cohort should produce 100 ready run units and zero missing units. Preview, run, and audit through code/run_logged.sh; use a unique per-unit log directory for each launch. Existing outputs are checked against the reference and skipped, so an interrupted batch is safely restartable. --overwrite is intentionally explicit and should be used only after reviewing an invalid existing derivative.
After the independent 100-run resampling audit passes, build the cross-dataset manifest. It contains one RF1 pre-resampling row per canonical Shared Reward BOLD, plus paired ds003745 pre-resampling and post-resampling/pre-blur rows:
python3 code/build_characterization_manifest.py \
--output logs/runlists/phase0-characterization-ready.tsv \
--missing-output logs/runlists/phase0-characterization-missing.tsvThe RF1 rows reference the authoritative Tedana-plus-confounds files under rf1-sra-linux2/derivatives/fsl/confounds_tedana; this repository does not copy them. The current inventory should produce 867 units: 667 RF1 session-01 pre-resampling, 100 ds003745 pre-resampling, and 100 ds003745 post-resampling/pre-blur. RF1 discovery is reconciled to rf1-sra-linux2/qc/run_qc.tsv, rather than globbing future sessions into the cohort.
Long batches should be launched through both run_logged.sh and nohup, with an explicit outer launcher log, so they survive an SSH disconnect:
nohup bash code/run_logged.sh \
--label phase0-baseline-smoothness-full \
--include-full-log -- \
python3 code/run_smoothness_batch.py \
--manifest logs/runlists/phase0-characterization-ready.tsv \
--jobs 8 \
--output-dir derivatives/qc/smoothness/run-level \
--log-dir logs/smoothness-current \
--work-root work/phase0-smoothness \
> logs/phase0-baseline-smoothness-full.nohup 2>&1 </dev/null &run_smoothness_batch.py delegates every unit to the authoritative RF1 measure_smoothness.sh, uses isolated AFNI work directories, writes one atomic result per unit, and verifies existing results before restart skips. Re-running the same command after interruption validates and skips completed units. audit_smoothness.py creates the Git-trackable consolidated table used for target evaluation. It records classic Gaussian and ACF estimates. Phase 0 approved a 6-mm total classic-FWHM target on 2026-08-23.
Build the production contract from the frozen characterization manifest. Only RF1 pre_resample and ds003745 post_resample_preblur rows are selected; each derivative is written in its owning repository.
python3 code/build_target_smoothing_manifest.py \
--output logs/runlists/target-smoothing-6mm-ready.tsv \
--missing-output logs/runlists/target-smoothing-6mm-missing.tsvThe current cohort should contain 767 units: 667 RF1 and 100 ds003745. Existing validated outputs are skipped, so the post-August catch-up should schedule only the two new sub-12032 runs. Launch with bounded AFNI concurrency and an SSH-safe outer log:
nohup bash code/run_logged.sh \
--label phase0-target-smoothing-6mm-full \
--include-full-log -- \
python3 code/run_target_smoothing_batch.py \
--manifest logs/runlists/target-smoothing-6mm-ready.tsv \
--jobs 8 \
--log-dir logs/target-smoothing-6mm-current \
--work-root work/target-smoothing-6mm \
--check \
python3 code/audit_target_smoothing.py \
--manifest logs/runlists/target-smoothing-6mm-ready.tsv \
--output logs/records/target-smoothing-6mm-audit.tsv \
--missing-output logs/records/target-smoothing-6mm-missing.tsv \
--fail-on-incomplete \
> logs/phase0-target-smoothing-6mm-full.nohup 2>&1 </dev/null &The runner validates existing output/QC pairs before skipping them, so the same command is restartable. Partial or invalid pairs stop with an explicit request to review and use --overwrite; they are never silently replaced. The audit requires output/mask geometry agreement and achieved classic combined FWHM within AFNI's documented ±10% approximation tolerance, while retaining the complete ACF diagnostics. A run outside that tolerance can pass only through an exact row in docs/smoothing_qc_exceptions.tsv, with the expected target, a narrow accepted measurement range, rationale, and an existing tracked evidence record; this never waives geometry or other QC failures.
AFNI normally chooses a subset of blurmaster volumes for speed. If a reviewed run repeatedly passes AFNI's internal stopping rule but the independent all-volume 3dFWHMx audit falls outside tolerance, a one-row retry may add --all-blurmaster --overwrite. This passes AFNI -bmall, making convergence use every volume. It is an exception mechanism, not the cohort default; the run record must document its use.
FEAT's smoothing field is expressed as FWHM, but its generated susan command receives spatial sigma in millimeters: FWHM / sqrt(8 ln 2), so 6 mm becomes 2.54777 mm. FEAT also uses a brightness threshold equal to 75% of the masked median, a temporal mean image as the one USAN image, 3D processing, median fallback, and a final brain-mask application. smooth_with_feat_susan.sh reproduces that stage directly on the already motion-corrected analysis BOLD; it deliberately does not repeat MCFLIRT, BET, intensity normalization, or temporal filtering.
This is not an equivalence test between two spellings of the same operation. AFNI 3dBlurToFWHM -FWHM 6 targets approximately 6 mm total measured classic smoothness. FEAT applies a nominal 6-mm SUSAN kernel to an already smooth image. For an ideal Gaussian kernel, the latter total is approximately sqrt(baseline^2 + 6^2). The pilot measures the nonlinear SUSAN result empirically with the same 3dFWHMx command used for the AFNI output.
The comparison manifest defaults to the highest-baseline analysis-ready run from each dataset. --scope all generalizes the same contract to the 765 analysis-ready inputs: 665 RF1 runs plus 100 post-wsinc5 ds003745 runs. The 865-row characterization table additionally contains the same 100 ds003745 runs on their unused native fMRIPrep grid; those rows are intentionally excluded from the production-method comparison. Comparison derivatives are target/method encoded and separate from production inputs.
Build and launch the two-run pilot after the 6-mm target manifest exists. The selected runs are currently RF1 sub-11720/ses-01/run-1 and ds003745 sub-118/run-02.
python3 code/build_susan_comparison_manifest.py \
--target-manifest logs/runlists/target-smoothing-6mm-ready.tsv \
--scope pilot \
--kernel-fwhm 6 \
--output logs/runlists/susan-vs-afni-6mm-pilot.tsv \
--missing-output logs/runlists/susan-vs-afni-6mm-pilot-missing.tsv
nohup bash code/run_logged.sh \
--label phase0-susan-vs-afni-6mm-pilot \
--include-full-log -- \
python3 code/run_susan_comparison.py \
--manifest logs/runlists/susan-vs-afni-6mm-pilot.tsv \
--jobs 2 \
--log-dir logs/susan-vs-afni-6mm-pilot \
--work-root work/susan-vs-afni-6mm-pilot \
--check \
python3 code/audit_susan_comparison.py \
--manifest logs/runlists/susan-vs-afni-6mm-pilot.tsv \
--output logs/records/susan-vs-afni-6mm-pilot.tsv \
--missing-output logs/records/susan-vs-afni-6mm-pilot-missing.tsv \
--fail-on-incomplete \
> logs/phase0-susan-vs-afni-6mm-pilot.nohup 2>&1 </dev/null &After the production AFNI target-smoothing launcher has stopped and its outputs have been reviewed, rebuild with --scope all. The expected contract is 765 ready and zero incomplete units. Use a separate log directory and request a compact dataset-level summary in addition to the 2,295 method-level rows:
python3 code/build_susan_comparison_manifest.py \
--target-manifest logs/runlists/target-smoothing-6mm-ready.tsv \
--scope all \
--kernel-fwhm 6 \
--output logs/runlists/susan-vs-afni-6mm-full.tsv \
--missing-output logs/runlists/susan-vs-afni-6mm-full-missing.tsv
nohup bash code/run_logged.sh \
--label phase0-susan-vs-afni-6mm-full \
--include-full-log -- \
python3 code/run_susan_comparison.py \
--manifest logs/runlists/susan-vs-afni-6mm-full.tsv \
--jobs 8 \
--log-dir logs/susan-vs-afni-6mm-full \
--work-root work/susan-vs-afni-6mm-full \
--check \
python3 code/audit_susan_comparison.py \
--manifest logs/runlists/susan-vs-afni-6mm-full.tsv \
--output logs/records/susan-vs-afni-6mm-full.tsv \
--summary-output logs/records/susan-vs-afni-6mm-full-summary.tsv \
--missing-output logs/records/susan-vs-afni-6mm-full-missing.tsv \
--fail-on-incomplete \
> logs/phase0-susan-vs-afni-6mm-full.nohup 2>&1 </dev/null &The production tSNR definition is voxelwise temporal mean divided by sample temporal standard deviation (ddof=1). It is measured from the approved desc-smoothToFWHM6_bold file that will enter FEAT. Summary statistics use the intersection of each run's fMRIPrep brain mask and one fixed full TemplateFlow MNI152NLin6Asym brain mask resampled by nearest neighbor to the exact RF1 grid.
Coverage has a distinct denominator because the historical Shared Reward workflow explicitly exempted inferior cerebellum and posterior brainstem. create_coverage_eligible_mask.py nearest-neighbor resamples the tracked masks/cerebellum-brainstem_mask.nii.gz to the RF1 grid and creates TemplateFlow brain AND NOT exemption. coverage_pct is run-mask overlap divided by this eligible mask. The exemption is not applied to tSNR or silently promoted to a statistical analysis mask. Motion is read from the named fMRIPrep desc-confounds_timeseries.tsv, not from RF1's intentionally headerless Tedana-plus-confounds FEAT matrix. The latter remains the nuisance input to L1 and is not duplicated here.
Create the non-participant common mask once. Review the find result before running the command; it must identify the TemplateFlow MNI152NLin6Asym brain mask rather than a different template:
template_mask=$(find "$TEMPLATEFLOW_HOME/tpl-MNI152NLin6Asym" -type f \
-name 'tpl-MNI152NLin6Asym_res-02_desc-brain_mask.nii.gz' \
| sort | head -n 1)
printf 'Template mask: %s\n' "$template_mask"
test -n "$template_mask" && test -f "$template_mask"
python3 code/create_common_analysis_mask.py \
--source-mask "$template_mask" \
--reference-grid "$REFERENCE_GRID" \
--output "$TSNR_REFERENCE_MASK" \
--json-output resources/tpl-MNI152NLin6Asym_space-RF1Grid_desc-brain_mask.json
python3 code/create_coverage_eligible_mask.py \
--template-mask "$TSNR_REFERENCE_MASK" \
--exemption-mask masks/cerebellum-brainstem_mask.nii.gz \
--reference-grid "$REFERENCE_GRID" \
--resampled-exemption-output "$COVERAGE_EXEMPTION_MASK" \
--eligible-mask-output "$COVERAGE_ELIGIBLE_MASK" \
--json-output resources/tpl-MNI152NLin6Asym_space-RF1Grid_desc-coverageEligible_mask.jsonBuild the 767-run QC contract, then launch it through nohup. The batch reads each smoothed 4D input once, delegates tSNR to the authoritative RF1 implementation, requires one confound row per BOLD volume, and calculates FD>0.5-mm volume fractions. It writes small restartable per-run JSON files under ignored derivatives/qc/.
python3 code/build_analysis_qc_manifest.py \
--output logs/runlists/analysis-qc-ready.tsv \
--missing-output logs/runlists/analysis-qc-missing.tsv
nohup bash code/run_logged.sh \
--label phase0-analysis-input-qc-full \
--include-full-log -- \
python3 code/run_analysis_qc_batch.py \
--manifest logs/runlists/analysis-qc-ready.tsv \
--jobs 8 \
--log-dir logs/analysis-qc-current \
--check \
python3 code/audit_analysis_qc.py \
--manifest logs/runlists/analysis-qc-ready.tsv \
--output logs/records/analysis-qc-run-level.tsv \
--summary-output logs/records/analysis-qc-dataset-summary.tsv \
--subject-output logs/records/analysis-qc-subject-level.tsv \
--missing-output logs/records/analysis-qc-missing.tsv \
--fail-on-incomplete \
> logs/phase0-analysis-input-qc-full.nohup 2>&1 </dev/null &The default audit reports the preregistration-consistent, dataset-specific 1.5×IQR review flags: low tSNR, high mean FD, and low fixed-mask coverage. FD>0.5-mm counts and fractions remain descriptive columns. Optional fixed coverage/high-motion warning cutoffs may be requested on the command line, but they are not enabled by default and must not be confused with the registered exclusion rules. Thresholds and all raw metrics are retained so exclusions can be reviewed scientifically. Missed-trial exclusions are handled separately from imaging quality: a run is excluded only when more than 25% of its trials are missed. After a complete audit, create the simple QC plots:
python3 code/plot_analysis_qc.py \
--input logs/records/analysis-qc-run-level.tsv \
--output-dir qcThe runner upgrades version-1/2 QC JSONs to the corrected coverage contract by rereading the small 3D run mask and eligible mask only; it does not reread an already-measured 4D BOLD file. The tracked qc/ outputs and compact logs/records/ tables should be committed after review. Large per-run JSON derivatives stay ignored.
Event QC is a separate gate from imaging QC. It reads source BIDS events from each owning dataset, writes model-specific full-trial derivatives only under this repository's ignored derivatives/harmonized/events, and never edits source BIDS. RF1 full trials span the validated decision onset through matching outcome offset; ds003745 retains the published trial-level onset and duration. A miss is represented as one full-trial nuisance event.
The registered task-compliance rule is strict: exclude a run only when more than 25% of its trials are missed. Exactly 25% remains usable. A participant is excluded on this basis only if every available run is excluded. Zero-count substantive conditions are reported explicitly for design review; they are not silently converted into a different scientific exclusion rule.
python3 code/build_event_qc_manifest.py \
--output logs/runlists/fulltrial-event-qc-ready.tsv \
--missing-output logs/runlists/fulltrial-event-qc-missing.tsv
nohup bash code/run_logged.sh \
--label phase0-fulltrial-event-qc \
--include-full-log -- \
python3 code/run_event_qc_batch.py \
--manifest logs/runlists/fulltrial-event-qc-ready.tsv \
--jobs 16 \
--log-dir logs/fulltrial-event-qc-current \
--check \
python3 code/audit_event_qc.py \
--manifest logs/runlists/fulltrial-event-qc-ready.tsv \
--output logs/records/fulltrial-event-qc-run-level.tsv \
--subject-output logs/records/fulltrial-event-qc-subject-level.tsv \
--missing-output logs/records/fulltrial-event-qc-missing.tsv \
--fail-on-incomplete \
> logs/phase0-fulltrial-event-qc.nohup 2>&1 </dev/null &The build step records established source gaps without failing by default. Add
--fail-on-missing only when an absolutely complete source inventory is expected.
The cohort freezer consumes both the ready audit and this source-gap table, so it
cannot silently promote a run with missing events.
The resulting run-level imaging and event tables remain separate evidence. A later explicit cohort-selection step must combine them before building L1 manifests; neither audit silently deletes data.
Ratings exclusions are subject-level and apply to both runs. The modern audit preserves the historical rules while removing the unsafe row-position heuristic: exclude for missing/empty ratings, identical ratings across all conditions, or aggregate loss ratings strictly greater than aggregate win ratings. Equality is allowed. Every selected source must contain all six partner (1, 2, 3) × trait/outcome (0 win, 1 loss) cells. Raw cell means/counts and the source SHA-256 are retained.
First retrieve the ds003745 ratings files through DataLad. Then build the discovery manifest. Exactly one source is required per subject; multiple candidates are written to the missing/ambiguous report and must be resolved with an explicit --ratings-map rather than by dropping rows or choosing a filename implicitly.
python3 code/build_ratings_qc_manifest.py \
--output logs/runlists/ratings-qc-ready.tsv \
--missing-output logs/runlists/ratings-qc-missing.tsv
python3 code/audit_ratings_qc.py \
--manifest logs/runlists/ratings-qc-ready.tsv \
--output logs/records/ratings-qc-subject-level.tsv \
--missing-output logs/records/ratings-qc-invalid.tsv \
--fail-on-incompleteAn explicit resolution map has three tab-separated columns: dataset, subject, and ratings_file. Rebuild the manifest with --ratings-map logs/runlists/ratings-source-resolutions.tsv after reviewing ambiguous candidates.
After the imaging, event, ratings, and nuisance audits are current, freeze the analysis inventories. This step reconciles the complete dynamic imaging inventory against either a completed event-QC row or an established source-missing row and fails if anything is unaccounted for. docs/curated_run_exclusions.tsv supplies provenance-backed task invalidations. Ratings are kept as a separate eligibility layer rather than being allowed to suppress otherwise valid activation/PPI estimation.
nohup bash code/run_logged.sh \
--label phase0-freeze-analysis-cohorts \
--include-full-log -- \
"$IMAGING_PYTHON" code/build_analysis_cohort.py \
> logs/phase0-freeze-analysis-cohorts.nohup 2>&1 </dev/null &The outputs are:
logs/runlists/L1-task-ready.tsvandL2-task-ready.tsv: task-valid activation/PPI inputs;logs/runlists/L1-ratings-ready.tsvandL2-ratings-ready.tsv: the task-valid subset passing the historical ratings gate;logs/runlists/L1-model-review-hold.tsv: otherwise usable runs with one or more zero-count substantive conditions;logs/records/analysis-run-dispositions.tsvandanalysis-subject-dispositions.tsv: the complete, mutually exclusive disposition audit.
Source-missing and missed-trial exclusions are run-level. A valid opposite run remains in L1 and is recorded as l1_passthrough; only two-run subjects receive fixed effects. Runs on model review hold do not enter a ready manifest. Imaging IQR flags are retained in the manifests but remain review information rather than automatic exclusions.
RF1 L1 models consume Linux2's existing headerless TedanaPlusConfounds.tsv matrices. Build the ds003745 conversion contract from the named confounds used for QC, generate the single-echo matrices, and audit their volume alignment:
python3 code/build_fsl_confounds_manifest.py \
--output logs/runlists/ds003745-fsl-confounds.tsv
python3 code/run_fsl_confounds_batch.py \
--manifest logs/runlists/ds003745-fsl-confounds.tsv \
--jobs 8 --log-dir logs/ds003745-fsl-confounds
python3 code/audit_fsl_confounds.py \
--manifest logs/runlists/ds003745-fsl-confounds.tsv \
--output logs/records/ds003745-fsl-confounds-audit.tsv \
--fail-on-incompleteRebuild build_analysis_cohort.py only after that audit passes. Generate three-column files, then pilot or launch activation and seed PPI in the same worker. Each worker runs activation first and begins PPI only after that activation command succeeds; --jobs 50 therefore means at most approximately 50 FEAT jobs, not 100.
python3 code/generate_l1_evs.py \
--manifest logs/runlists/L1-task-ready.tsv
nohup bash code/run_logged.sh \
--label pooled-L1-activation-PPI-vs --include-full-log -- \
bash code/run_L1stats.sh \
--manifest logs/runlists/L1-task-ready.tsv \
--ppi-seed vs --jobs 50 --log-dir logs/L1-current \
> logs/pooled-L1-activation-PPI-vs.nohup 2>&1 </dev/null &After both L1 audits pass, run activation and PPI fixed effects together for the two-run rows. FSLSUB_PARALLEL=1 is enforced inside L2stats.sh; --jobs remains the only outer concurrency control. One-run rows are reported and intentionally skipped because their L1 cope is already the subject-level estimate.
nohup bash code/run_logged.sh \
--label pooled-L2-activation-PPI-vs --include-full-log -- \
bash code/run_L2stats.sh \
--manifest logs/runlists/L2-task-ready.tsv \
--ppi-seed vs --jobs 20 --log-dir logs/L2-current \
> logs/pooled-L2-activation-PPI-vs.nohup 2>&1 </dev/null &