@brainpilot/skills 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/01_Meta-Skills/academic-research-hub/SKILL.md +108 -0
- package/skills/01_Meta-Skills/academic-research-hub/scripts/requirements.txt +17 -0
- package/skills/01_Meta-Skills/academic-research-hub/scripts/research.py +781 -0
- package/skills/01_Meta-Skills/beautiful-log/SKILL.md +64 -0
- package/skills/01_Meta-Skills/beautiful-log/scripts/beautiful_log.py +274 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/SKILL.md +130 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/config.template.yaml +54 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/top5_digest_template.md +5 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/build_top5_digest.py +300 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/common.py +137 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/merge_results.py +106 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/run_pipeline.py +177 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_arxiv.py +162 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_pubmed.py +202 -0
- package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/SKILL.md +173 -0
- package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/scripts/normalize_data.py +874 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/SKILL.md +134 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/confirmation-prompts.md +31 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/output-patterns.md +45 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_markdown_deliverables.py +41 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_research_log.py +84 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_summary_md.py +63 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/extract_pdf_bundle.py +140 -0
- package/skills/01_Meta-Skills/experiment-controller/SKILL.md +140 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/SKILL.md +366 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/entity_resolution.py +120 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/extraction_prompt_template.txt +19 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/graph_query.py +106 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/hypothesis_cli_reference.py +42 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/new_data_source_template.py +116 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/requirements.txt +15 -0
- package/skills/01_Meta-Skills/method-design/SKILL.md +61 -0
- package/skills/01_Meta-Skills/multi-search-engine/SKILL.md +119 -0
- package/skills/01_Meta-Skills/research-idea/SKILL.md +65 -0
- package/skills/05_EEG_ERP/eeg-skill/SKILL.md +197 -0
- package/skills/05_EEG_ERP/meg-skill/SKILL.md +188 -0
- package/skills/05_EEG_ERP/meg-skill/scripts/time_frequency.py +223 -0
- package/skills/05_EEG_ERP/mne-eeg-tool/SKILL.md +165 -0
- package/skills/05_EEG_ERP/mne-eeg-tool/scripts/eeg_pipeline_reference.py +231 -0
- package/skills/05_EEG_ERP/seed-iv-skill/SKILL.md +184 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/classify_seed_iv.py +154 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/extract_seed_iv_features.py +190 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/validate_seed_iv.py +102 -0
- package/skills/05_EEG_ERP/seed-vig-skill/SKILL.md +182 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/classify_seed_vig.py +165 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/extract_seed_vig_features.py +185 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/validate_seed_vig.py +88 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/SKILL.md +308 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/abcd_qc_summary.py +449 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/extract_abcd_phenotype.py +292 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/reorganize_abcd.py +387 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/SKILL.md +302 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/abide_qc_summary.py +317 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/extract_abide_phenotype.py +267 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/reorganize_abide.py +387 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/SKILL.md +244 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/adhd200_qc_summary.py +98 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/extract_adhd200_phenotype.py +134 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/reorganize_adhd200.py +206 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/SKILL.md +358 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_adni_task_files.py +1305 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_vqa_from_tasks.py +766 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/reorganize_adni.py +491 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/SKILL.md +295 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/aibl_qc_summary.py +260 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/extract_aibl_phenotype.py +365 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/reorganize_aibl.py +394 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/SKILL.md +292 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/aomic_qc_summary.py +258 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/extract_aomic_phenotype.py +284 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/reorganize_aomic.py +322 -0
- package/skills/06_fMRI_Neuroimaging/asl-skill/SKILL.md +168 -0
- package/skills/06_fMRI_Neuroimaging/asl-skill/scripts/compute_cbf.py +224 -0
- package/skills/06_fMRI_Neuroimaging/bids-organizer/SKILL.md +241 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/SKILL.md +186 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/bold5000_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/extract_bold5000_stimulus.py +125 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/reorganize_bold5000.py +102 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/SKILL.md +213 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/camcan_qc_summary.py +131 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/extract_camcan_phenotype.py +145 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/validate_camcan.py +141 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/SKILL.md +201 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/cobre_qc_summary.py +95 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/extract_cobre_phenotype.py +104 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/reorganize_cobre.py +140 -0
- package/skills/06_fMRI_Neuroimaging/conn-tool/SKILL.md +180 -0
- package/skills/06_fMRI_Neuroimaging/dcm2nii/SKILL.md +189 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/SKILL.md +183 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/dmt_har_med_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/extract_dmt_har_med_phenotype.py +121 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/reorganize_dmt_har_med.py +125 -0
- package/skills/06_fMRI_Neuroimaging/dwi-skill/SKILL.md +359 -0
- package/skills/06_fMRI_Neuroimaging/fmri-skill/SKILL.md +371 -0
- package/skills/06_fMRI_Neuroimaging/fmriprep-tool/SKILL.md +228 -0
- package/skills/06_fMRI_Neuroimaging/freesurfer-tool/SKILL.md +286 -0
- package/skills/06_fMRI_Neuroimaging/freesurfer-tool/scripts/freesurfer_processor.py +145 -0
- package/skills/06_fMRI_Neuroimaging/fsl-tool/SKILL.md +208 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/SKILL.md +271 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/extract_hbn_phenotype.py +107 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/hbn_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/reorganize_hbn.py +150 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/extract_hcpa_phenotype.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/hcpa_qc_summary.py +120 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/reorganize_hcpa.py +155 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/extract_hcpd_phenotype.py +148 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/hcpd_qc_summary.py +125 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/reorganize_hcpd.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/SKILL.md +215 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/extract_hcpep_phenotype.py +157 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/hcpep_qc_summary.py +143 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/reorganize_hcpep.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcppipeline-tool/SKILL.md +217 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/SKILL.md +214 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/extract_hcpya_phenotype.py +190 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/hcpya_qc_summary.py +152 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/reorganize_hcpya.py +203 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/SKILL.md +198 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/ixi_qc_summary.py +137 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/reorganize_ixi.py +190 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/SKILL.md +191 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/extract_mnd_phenotype.py +143 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/mnd_qc_summary.py +120 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/validate_mnd.py +107 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/SKILL.md +203 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/analyze_lesions.py +119 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/longitudinal_lesion.py +148 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/mschallenge_qc_summary.py +132 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/validate_mschallenge.py +116 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/SKILL.md +184 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/atlas_coordinate_reference.py +61 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/freesurfer_io_reference.py +34 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/nifti_inspection_reference.py +35 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/SKILL.md +205 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/extract_nifd_phenotype.py +132 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/nifd_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/validate_nifd.py +111 -0
- package/skills/06_fMRI_Neuroimaging/nii2dcm/SKILL.md +143 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/SKILL.md +266 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/connectome_reference.py +65 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/denoise_timeseries_reference.py +58 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/hierarchical_parcellation_reference.py +53 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/kmeans_parcellation_reference.py +53 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/preprocess_bold_reference.py +76 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_dictlearning_reference.py +56 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_ica_reference.py +59 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/second_level_glm_reference.py +58 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/spacenet_classifier_reference.py +59 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/svm_classifier_reference.py +60 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/task_glm_reference.py +63 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/zalff_summary_reference.py +109 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/extract_nsd_stimulus.py +171 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/nsd_qc_summary.py +142 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/validate_nsd.py +142 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/SKILL.md +205 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/extract_oasis_phenotype.py +126 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/oasis_qc_summary.py +115 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/validate_oasis.py +119 -0
- package/skills/06_fMRI_Neuroimaging/pet-skill/SKILL.md +173 -0
- package/skills/06_fMRI_Neuroimaging/pet-skill/scripts/compute_suvr.py +202 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/SKILL.md +206 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/extract_pnc_phenotype.py +136 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/pnc_qc_summary.py +116 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/validate_pnc.py +120 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/SKILL.md +209 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/extract_ppmi_phenotype.py +138 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/ppmi_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/validate_ppmi.py +117 -0
- package/skills/06_fMRI_Neuroimaging/qsiprep-tool/SKILL.md +320 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/SKILL.md +215 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/extract_rest_mdd_phenotype.py +132 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/harmonize_sites.py +152 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/rest_mdd_qc_summary.py +124 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/validate_rest_mdd.py +103 -0
- package/skills/06_fMRI_Neuroimaging/smri-skill/SKILL.md +302 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/SKILL.md +204 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/extract_tcp_phenotype.py +139 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/tcp_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/validate_tcp.py +99 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/SKILL.md +217 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/extract_ucla_cnp_phenotype.py +145 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/ucla_cnp_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/validate_ucla_cnp.py +113 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/SKILL.md +310 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/build_ukb_survival.py +210 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_cases.py +308 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_phenotype.py +232 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/ukb_qc_summary.py +158 -0
- package/skills/06_fMRI_Neuroimaging/wmh-segmentation/SKILL.md +133 -0
- package/skills/07_Computational_Modeling/detrending/SKILL.md +118 -0
- package/skills/07_Computational_Modeling/dictlearning/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/filtering/SKILL.md +121 -0
- package/skills/07_Computational_Modeling/glm/SKILL.md +153 -0
- package/skills/07_Computational_Modeling/hierarchical/SKILL.md +121 -0
- package/skills/07_Computational_Modeling/ica/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/kmeans/SKILL.md +119 -0
- package/skills/07_Computational_Modeling/run_models/SKILL.md +427 -0
- package/skills/07_Computational_Modeling/spacenet/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/svm/SKILL.md +120 -0
- package/skills/08_Computational_Neuroscience/brain_gnn/SKILL.md +183 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/SKILL.md +239 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/dti_metrics_reference.py +70 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/load_and_mask_reference.py +76 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/roi_stats_reference.py +59 -0
- package/skills/08_Computational_Neuroscience/fm_app/SKILL.md +195 -0
- package/skills/08_Computational_Neuroscience/neurostorm/SKILL.md +151 -0
- package/skills/13_Visualization/brain-visualization/SKILL.md +191 -0
- package/skills/13_Visualization/brain-visualization/scripts/connectome_reference.py +108 -0
- package/skills/13_Visualization/brain-visualization/scripts/freesurfer_ply_reference.py +54 -0
- package/skills/13_Visualization/brain-visualization/scripts/zalff_summary_reference.py +116 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/SKILL.md +78 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/assets/naturecomm_figures.tex +74 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/scripts/layout_results_foldered.py +579 -0
- package/skills/14_Writing/overleaf-skill/SKILL.md +184 -0
- package/skills/14_Writing/overleaf-skill/scripts/install.sh +30 -0
- package/skills/14_Writing/paper-writing/SKILL.md +146 -0
- package/skills/14_Writing/paper-writing/scripts/data_statement_templates.py +164 -0
- package/skills/14_Writing/paper-writing/scripts/figure_templates.py +315 -0
- package/skills/14_Writing/paper-writing/scripts/nature_figure_style.py +214 -0
- package/skills/14_Writing/paper-writing/scripts/section_phrasebank.py +246 -0
- package/skills/16_Animal_Behavior/deeplabcut/SKILL.md +154 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/3d-pose.md +89 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/maDLC.md +123 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/modelzoo.md +98 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/standard-pipeline.md +165 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/utilities.md +146 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/SKILL.md +274 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.html +112 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.md +21 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/cluster-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/heatmap-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/integrated-interpretation.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/overview.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/project-summary.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/radar-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/raw-trajectory.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/sample-check.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/single-subject-section.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/stats-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/epm.md +52 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/fst.md +37 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/nor.md +39 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/oft.md +43 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tcst.md +45 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tst.md +36 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/input-types.md +59 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/interpretation-guardrails.md +45 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/metadata-schema.md +57 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/report-sections.md +86 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/section-selection-rules.md +169 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/build_report_manifest.py +27 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/render_report.py +34 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/report_utils.py +1121 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/SKILL.md +390 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/reference_code.py +98 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-pose-estimation/SKILL.md +336 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/README.md +21 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/SKILL.md +41 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/batch_kinematic_generator.py +663 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/config.json +19 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/generate_kinematic_parameter.py +401 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/kinematic_generator.py +265 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/SKILL.md +72 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/references/config.example.toml +56 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params.py +232 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params_from_config.py +236 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/SKILL.md +68 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/references/notes.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/scripts/plot_h5_radar.py +513 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/SKILL.md +52 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/config.toml +81 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/references/stats-rule.md +18 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_inspect.py +79 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_batch.py +624 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_stats.py +438 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/SKILL.md +280 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_trajectory.py +790 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_velocity.py +855 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.csv +101 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.h5 +0 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_data_readme.md +126 -0
package/skills/13_Visualization/ethoclaw-paper-figure-layout/scripts/layout_results_foldered.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
r"""Auto-layout images into a Nature Communications-ish PDF using the TypeTex LaTeX compiler.
|
|
3
|
+
|
|
4
|
+
This script started as a simple "one image per page" foldered layout.
|
|
5
|
+
It has been upgraded to support a compact Nature-style *multi-panel figure* layout:
|
|
6
|
+
- Tight, compact page usage (no "one page per image" by default)
|
|
7
|
+
- Subpanel letters (a, b, c, ...) aligned and consistent
|
|
8
|
+
- One figure caption containing: a short title + per-panel descriptions
|
|
9
|
+
- Sensible defaults: if the user doesn't specify, pick **1 representative image per type**
|
|
10
|
+
("type" = subfolder under the input root) instead of dumping everything.
|
|
11
|
+
|
|
12
|
+
Usage
|
|
13
|
+
python3 layout_results_foldered.py \
|
|
14
|
+
--input "/path/to/2_results" \
|
|
15
|
+
--output "/path/to/out.pdf" \
|
|
16
|
+
--title "Results"
|
|
17
|
+
|
|
18
|
+
Modes
|
|
19
|
+
- compact (default): multi-panel figures; representative images per folder.
|
|
20
|
+
- foldered: legacy behavior (all images, one per block, page breaks per folder).
|
|
21
|
+
|
|
22
|
+
Notes
|
|
23
|
+
- The TypeTex LaTeX environment defaults to latexmk+xelatex, but xelatex is not installed.
|
|
24
|
+
We provide a .latexmkrc to force pdflatex.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import base64
|
|
31
|
+
import math
|
|
32
|
+
import re
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Iterable
|
|
35
|
+
|
|
36
|
+
import requests
|
|
37
|
+
|
|
38
|
+
API_URL = "https://studio-intrinsic--typetex-compile-app.modal.run/public/compile/latex"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def b64_file(p: Path) -> str:
|
|
42
|
+
return base64.b64encode(p.read_bytes()).decode("utf-8")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_image(p: Path) -> bool:
|
|
46
|
+
return p.suffix.lower() in {".png", ".jpg", ".jpeg", ".webp"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def natural_key(s: str):
|
|
50
|
+
# sort "img2" before "img10"
|
|
51
|
+
return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", s)]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def caption_from_filename(name: str) -> str:
|
|
55
|
+
stem = Path(name).stem
|
|
56
|
+
# make it readable: underscores/dashes -> spaces
|
|
57
|
+
stem = re.sub(r"[_-]+", " ", stem).strip()
|
|
58
|
+
return stem
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def nice_title(s: str) -> str:
|
|
62
|
+
# folder names like heatmap_velocity -> Heatmap velocity
|
|
63
|
+
# nested names like radar/group_means -> Radar / group means
|
|
64
|
+
s = s.replace("/", " / ")
|
|
65
|
+
s = s.replace("_", " ").replace("-", " ").strip()
|
|
66
|
+
s = re.sub(r"\s+", " ", s)
|
|
67
|
+
return s[:1].upper() + s[1:] if s else s
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def find_groups(root: Path) -> list[tuple[str, list[Path]]]:
|
|
71
|
+
"""Return [(group_name, [images...])]. group_name is folder name relative to root.
|
|
72
|
+
|
|
73
|
+
We recurse to include nested result folders like:
|
|
74
|
+
2_results/radar/group_means
|
|
75
|
+
|
|
76
|
+
Also include images directly under root as a group (root.name).
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
groups: list[tuple[str, list[Path]]] = []
|
|
80
|
+
|
|
81
|
+
# include any directory (including nested) that contains at least 1 image
|
|
82
|
+
for d in sorted([p for p in root.rglob("*") if p.is_dir()], key=lambda p: natural_key(str(p.relative_to(root)))):
|
|
83
|
+
imgs = sorted([p for p in d.iterdir() if p.is_file() and is_image(p)], key=lambda p: natural_key(p.name))
|
|
84
|
+
if imgs:
|
|
85
|
+
rel = str(d.relative_to(root))
|
|
86
|
+
groups.append((rel, imgs))
|
|
87
|
+
|
|
88
|
+
# root images
|
|
89
|
+
root_imgs = sorted([p for p in root.iterdir() if p.is_file() and is_image(p)], key=lambda p: natural_key(p.name))
|
|
90
|
+
if root_imgs:
|
|
91
|
+
groups.insert(0, (root.name, root_imgs))
|
|
92
|
+
|
|
93
|
+
return groups
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _rep_score(p: Path) -> tuple[int, list]:
|
|
97
|
+
n = p.name.lower()
|
|
98
|
+
bonus = 0
|
|
99
|
+
if "colorbar" in n or "color_bar" in n:
|
|
100
|
+
bonus += 1000
|
|
101
|
+
if any(t in n for t in ["group", "mean", "avg", "summary"]):
|
|
102
|
+
bonus -= 50
|
|
103
|
+
if "control" in n or n.startswith("con"):
|
|
104
|
+
bonus -= 10
|
|
105
|
+
if "model" in n:
|
|
106
|
+
bonus -= 5
|
|
107
|
+
return (bonus, natural_key(p.name))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def infer_subtype_key(p: Path) -> str:
|
|
111
|
+
"""Infer a semantic subtype key from filename.
|
|
112
|
+
|
|
113
|
+
Goal: default compact layout should keep one representative of each *kind* of figure,
|
|
114
|
+
not merely one per folder. Example: x-Axis / y-Axis / z-Axis should all survive.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
stem = caption_from_filename(p.name).lower()
|
|
118
|
+
tokens = [t for t in re.split(r"\s+", stem) if t]
|
|
119
|
+
|
|
120
|
+
# remove obvious sample/id/timestamp markers from the edges
|
|
121
|
+
drop_words = {"rec", "sample", "subject", "mouse", "rat", "control", "model", "con"}
|
|
122
|
+
|
|
123
|
+
def droppable(tok: str) -> bool:
|
|
124
|
+
return (
|
|
125
|
+
tok in drop_words
|
|
126
|
+
or tok.isdigit()
|
|
127
|
+
or re.fullmatch(r"\d{6,}", tok) is not None
|
|
128
|
+
or re.fullmatch(r"[a-z]*\d+[a-z\d]*", tok) is not None
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
while tokens and droppable(tokens[0]):
|
|
132
|
+
tokens.pop(0)
|
|
133
|
+
while tokens and droppable(tokens[-1]):
|
|
134
|
+
tokens.pop()
|
|
135
|
+
|
|
136
|
+
if not tokens:
|
|
137
|
+
return "sample"
|
|
138
|
+
return " ".join(tokens)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def pick_representatives(imgs: list[Path], k: int) -> list[Path]:
|
|
142
|
+
"""Pick representatives from a folder.
|
|
143
|
+
|
|
144
|
+
Default semantics: keep up to k representatives *per inferred subtype* rather than
|
|
145
|
+
blindly taking only k files for the whole folder.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if k <= 0 or not imgs:
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
buckets: dict[str, list[Path]] = {}
|
|
152
|
+
for p in imgs:
|
|
153
|
+
key = infer_subtype_key(p)
|
|
154
|
+
buckets.setdefault(key, []).append(p)
|
|
155
|
+
|
|
156
|
+
picked: list[Path] = []
|
|
157
|
+
for key in sorted(buckets, key=natural_key):
|
|
158
|
+
ranked = sorted(buckets[key], key=_rep_score)
|
|
159
|
+
picked.extend(ranked[:k])
|
|
160
|
+
|
|
161
|
+
return picked
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def build_compact_entries(groups: list[tuple[str, list[Path]]], max_per_type: int) -> list[tuple[str, list[Path], bool]]:
|
|
165
|
+
"""Return compact-layout entries.
|
|
166
|
+
|
|
167
|
+
Each entry is: (group_name, image_list, is_colorbar_bundle)
|
|
168
|
+
|
|
169
|
+
Behavior:
|
|
170
|
+
- if max_per_type <= 0: include *all* images per group
|
|
171
|
+
- if a group contains colorbar + >=1 other image, create one special bundled entry:
|
|
172
|
+
[first up to 3 non-colorbar images] + [colorbar]
|
|
173
|
+
- remaining images become normal one-image entries
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
entries: list[tuple[str, list[Path], bool]] = []
|
|
177
|
+
|
|
178
|
+
for group_name, imgs in groups:
|
|
179
|
+
imgs0 = sorted(imgs, key=lambda p: natural_key(p.name))
|
|
180
|
+
colorbars = [p for p in imgs0 if "colorbar" in p.name.lower() or "color_bar" in p.name.lower()]
|
|
181
|
+
non_color = [p for p in imgs0 if p not in colorbars]
|
|
182
|
+
|
|
183
|
+
if max_per_type <= 0:
|
|
184
|
+
chosen_non_color = non_color
|
|
185
|
+
else:
|
|
186
|
+
chosen_non_color = pick_representatives(non_color, max_per_type)
|
|
187
|
+
|
|
188
|
+
if colorbars and chosen_non_color:
|
|
189
|
+
# bundle up to 3 heatmaps + 1 colorbar into one compact panel
|
|
190
|
+
bundle_imgs = chosen_non_color[:3] + [colorbars[0]]
|
|
191
|
+
entries.append((group_name, bundle_imgs, True))
|
|
192
|
+
bundled_set = set(bundle_imgs[:-1])
|
|
193
|
+
leftovers = [p for p in chosen_non_color if p not in bundled_set]
|
|
194
|
+
for p in leftovers:
|
|
195
|
+
entries.append((group_name, [p], False))
|
|
196
|
+
else:
|
|
197
|
+
pool = chosen_non_color if non_color else ([] if max_per_type > 0 else imgs0)
|
|
198
|
+
for p in pool:
|
|
199
|
+
entries.append((group_name, [p], False))
|
|
200
|
+
|
|
201
|
+
return entries
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def chunked(seq: list, n: int) -> list[list]:
|
|
205
|
+
return [seq[i : i + n] for i in range(0, len(seq), n)]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def tex_sanitize_filename(name: str) -> str:
|
|
209
|
+
"""Generate a LaTeX-friendly filename.
|
|
210
|
+
|
|
211
|
+
Rationale: panel overlay (overpic) + some LaTeX setups can behave badly with
|
|
212
|
+
spaces / non-ascii in filenames. We always map input images to safe names
|
|
213
|
+
inside the compilation sandbox.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
# keep extension
|
|
217
|
+
p = Path(name)
|
|
218
|
+
stem = re.sub(r"[^A-Za-z0-9._-]+", "_", p.stem)
|
|
219
|
+
stem = re.sub(r"_+", "_", stem).strip("_")
|
|
220
|
+
ext = p.suffix.lower() if p.suffix else ".png"
|
|
221
|
+
if not stem:
|
|
222
|
+
stem = "img"
|
|
223
|
+
return f"{stem}{ext}"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def build_tex_compact(
|
|
227
|
+
title: str,
|
|
228
|
+
panels: list[tuple[str, list[Path], bool]],
|
|
229
|
+
fig_title: str,
|
|
230
|
+
cols: int = 2,
|
|
231
|
+
panels_per_figure: int = 6,
|
|
232
|
+
) -> str:
|
|
233
|
+
"""Build a compact, Nature-like multi-panel layout.
|
|
234
|
+
|
|
235
|
+
panels entries are: (group_name, image_list, is_colorbar_bundle)
|
|
236
|
+
- normal panel: image_list has 1 image
|
|
237
|
+
- colorbar bundle: image_list has N heatmaps + 1 colorbar
|
|
238
|
+
|
|
239
|
+
Important differences vs LaTeX floats:
|
|
240
|
+
- We avoid figure/figure* floats entirely to prevent "blank first page" issues.
|
|
241
|
+
- We render a manual "Fig. X | Title" line ABOVE the panels.
|
|
242
|
+
- Panel descriptions are printed BELOW as a compact paragraph.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
preamble_path = Path(__file__).resolve().parents[1] / "assets" / "naturecomm_figures.tex"
|
|
246
|
+
preamble = preamble_path.read_text(encoding="utf-8")
|
|
247
|
+
|
|
248
|
+
# Use 1-column for stable compact layout; keep NatureComm-ish header/geometry.
|
|
249
|
+
preamble = preamble.replace("\\documentclass[9pt,twocolumn]{article}", "\\documentclass[9pt]{article}")
|
|
250
|
+
|
|
251
|
+
# We avoid in-image overlays (overpic) for panel letters because overlays
|
|
252
|
+
# can get clipped/lost near the top edge depending on PDF renderer/cropping.
|
|
253
|
+
# Panel letters are rendered as normal text ABOVE each image instead.
|
|
254
|
+
|
|
255
|
+
parts: list[str] = [preamble, "\\begin{document}\n"]
|
|
256
|
+
|
|
257
|
+
# Tighten vertical whitespace globally.
|
|
258
|
+
parts.append(
|
|
259
|
+
"\\setlength{\\parindent}{0pt}\n"
|
|
260
|
+
"\\setlength{\\parskip}{0pt}\n"
|
|
261
|
+
"\\setlength{\\textfloatsep}{2.5mm}\n"
|
|
262
|
+
"\\setlength{\\intextsep}{2.5mm}\n"
|
|
263
|
+
"\\setlength{\\floatsep}{2.0mm}\n"
|
|
264
|
+
"\\captionsetup[figure]{skip=1.2mm}\n"
|
|
265
|
+
"% panel label box padding\n"
|
|
266
|
+
"\\setlength{\\fboxsep}{1.2pt}\n\n"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Title as a simple heading (kept, but should not create blank pages since we use no floats).
|
|
270
|
+
if title:
|
|
271
|
+
parts.append("\\section*{" + title.replace("_", "\\_") + "}\n")
|
|
272
|
+
|
|
273
|
+
parts.append("% Auto-generated: compact multi-panel figure layout\n\n")
|
|
274
|
+
|
|
275
|
+
cols = max(1, min(4, int(cols)))
|
|
276
|
+
panels_per_figure = max(1, int(panels_per_figure))
|
|
277
|
+
|
|
278
|
+
# panel width per column
|
|
279
|
+
if cols == 1:
|
|
280
|
+
w = 0.94
|
|
281
|
+
elif cols == 2:
|
|
282
|
+
w = 0.485
|
|
283
|
+
elif cols == 3:
|
|
284
|
+
w = 0.322
|
|
285
|
+
else:
|
|
286
|
+
w = 0.24
|
|
287
|
+
|
|
288
|
+
# Figure chunks
|
|
289
|
+
for fig_idx, chunk in enumerate(chunked(panels, panels_per_figure), start=1):
|
|
290
|
+
cap_title = fig_title.strip() or title.strip() or "Results"
|
|
291
|
+
cap_title_tex = cap_title.replace("_", "\\_")
|
|
292
|
+
|
|
293
|
+
# --- Fig title ABOVE ---
|
|
294
|
+
parts.append("\\vspace{1mm}\n")
|
|
295
|
+
parts.append(f"\\textbf{{Fig. {fig_idx} | {cap_title_tex}}}\\\\[1.5mm]\n")
|
|
296
|
+
|
|
297
|
+
# --- Panels grid ---
|
|
298
|
+
parts.append("\\begin{center}\n")
|
|
299
|
+
|
|
300
|
+
rows = int(math.ceil(len(chunk) / cols))
|
|
301
|
+
for r in range(rows):
|
|
302
|
+
for c in range(cols):
|
|
303
|
+
i = r * cols + c
|
|
304
|
+
if i >= len(chunk):
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
group_name, img_list, is_bundle = chunk[i]
|
|
308
|
+
letter = chr(ord("a") + i)
|
|
309
|
+
|
|
310
|
+
parts.append(f"\\begin{{minipage}}[t]{{{w}\\textwidth}}\\vspace{{0pt}}\n")
|
|
311
|
+
# Panel label: white background (no border) + positive spacing so it stays ABOVE the image.
|
|
312
|
+
parts.append("\\raggedright\\colorbox{white}{\\textbf{" + letter + "}}\\\\[0.8mm]\n")
|
|
313
|
+
parts.append("\\centering\n")
|
|
314
|
+
if is_bundle and len(img_list) >= 2:
|
|
315
|
+
colorbar = img_list[-1]
|
|
316
|
+
heatmaps = img_list[:-1]
|
|
317
|
+
if len(heatmaps) == 1:
|
|
318
|
+
parts.append(
|
|
319
|
+
"\\setlength{\\tabcolsep}{2pt}\n"
|
|
320
|
+
"\\begin{tabular}{@{}cc@{}}\n"
|
|
321
|
+
f"\\includegraphics[width=0.82\\linewidth]{{{heatmaps[0].name}}} & "
|
|
322
|
+
f"\\includegraphics[width=0.10\\linewidth]{{{colorbar.name}}} \\\\n"
|
|
323
|
+
"\\end{tabular}\n"
|
|
324
|
+
)
|
|
325
|
+
elif len(heatmaps) == 2:
|
|
326
|
+
parts.append(
|
|
327
|
+
"\\setlength{\\tabcolsep}{2pt}\n"
|
|
328
|
+
"\\begin{tabular}{@{}ccc@{}}\n"
|
|
329
|
+
f"\\includegraphics[width=0.40\\linewidth]{{{heatmaps[0].name}}} & "
|
|
330
|
+
f"\\includegraphics[width=0.40\\linewidth]{{{heatmaps[1].name}}} & "
|
|
331
|
+
f"\\includegraphics[width=0.08\\linewidth]{{{colorbar.name}}} \\\\n"
|
|
332
|
+
"\\end{tabular}\n"
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
parts.append(
|
|
336
|
+
"\\setlength{\\tabcolsep}{2pt}\n"
|
|
337
|
+
"\\begin{tabular}{@{}cccc@{}}\n"
|
|
338
|
+
f"\\includegraphics[width=0.26\\linewidth]{{{heatmaps[0].name}}} & "
|
|
339
|
+
f"\\includegraphics[width=0.26\\linewidth]{{{heatmaps[1].name}}} & "
|
|
340
|
+
f"\\includegraphics[width=0.26\\linewidth]{{{heatmaps[2].name}}} & "
|
|
341
|
+
f"\\includegraphics[width=0.08\\linewidth]{{{colorbar.name}}} \\\\n"
|
|
342
|
+
"\\end{tabular}\n"
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
img = img_list[0]
|
|
346
|
+
parts.append(f"\\includegraphics[width=\\linewidth]{{{img.name}}}\n")
|
|
347
|
+
parts.append("\\end{minipage}")
|
|
348
|
+
|
|
349
|
+
if c != cols - 1 and (i + 1) < len(chunk):
|
|
350
|
+
parts.append("\\hfill\n")
|
|
351
|
+
|
|
352
|
+
parts.append("\\\\[2.2mm]\n")
|
|
353
|
+
|
|
354
|
+
parts.append("\\end{center}\n")
|
|
355
|
+
|
|
356
|
+
# --- Panel descriptions BELOW ---
|
|
357
|
+
panel_descs: list[str] = []
|
|
358
|
+
for i, (group_name, img_list, is_bundle) in enumerate(chunk):
|
|
359
|
+
letter = chr(ord("a") + i)
|
|
360
|
+
g = nice_title(group_name)
|
|
361
|
+
if is_bundle and len(img_list) >= 2:
|
|
362
|
+
heat_desc = ", ".join(caption_from_filename(p.name) for p in img_list[:-1])
|
|
363
|
+
cb_desc = caption_from_filename(img_list[-1].name)
|
|
364
|
+
desc = f"{heat_desc}; shared {cb_desc}"
|
|
365
|
+
else:
|
|
366
|
+
desc = caption_from_filename(img_list[0].name)
|
|
367
|
+
panel_descs.append(f"\\textbf{{{letter}}} {g}: {desc}")
|
|
368
|
+
|
|
369
|
+
desc_line = "; ".join(panel_descs).replace("_", "\\_") + "."
|
|
370
|
+
parts.append("{\\fontsize{8}{9}\\selectfont " + desc_line + "}\\par\n")
|
|
371
|
+
|
|
372
|
+
# Spacing between figures; allow more than one figure per page when small.
|
|
373
|
+
parts.append("\\vspace{3.0mm}\n\n")
|
|
374
|
+
|
|
375
|
+
parts.append("\\end{document}\n")
|
|
376
|
+
return "".join(parts)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def build_tex_foldered(title: str, groups: list[tuple[str, list[Path]]]) -> str:
|
|
380
|
+
"""Legacy mode: folder-by-folder, one image per block, page breaks."""
|
|
381
|
+
|
|
382
|
+
preamble_path = Path(__file__).resolve().parents[1] / "assets" / "naturecomm_figures.tex"
|
|
383
|
+
preamble = preamble_path.read_text(encoding="utf-8")
|
|
384
|
+
|
|
385
|
+
# Force one-column (legacy behavior), avoid float placement oddities.
|
|
386
|
+
preamble = preamble.replace("\\documentclass[9pt,twocolumn]{article}", "\\documentclass[9pt]{article}")
|
|
387
|
+
|
|
388
|
+
parts: list[str] = [preamble, "\\begin{document}\n"]
|
|
389
|
+
|
|
390
|
+
if title:
|
|
391
|
+
parts.append("\\section*{" + title.replace("_", "\\_") + "}\n")
|
|
392
|
+
|
|
393
|
+
parts.append("% Auto-generated: folder-grouped image layout (legacy)\n")
|
|
394
|
+
|
|
395
|
+
for group_name, imgs in groups:
|
|
396
|
+
safe_group = group_name.replace("_", "\\_")
|
|
397
|
+
parts.append("\\subsection*{" + safe_group + "}\n")
|
|
398
|
+
|
|
399
|
+
for img in imgs:
|
|
400
|
+
cap = caption_from_filename(img.name).replace("_", "\\_")
|
|
401
|
+
parts.append(
|
|
402
|
+
"\\begin{center}\n"
|
|
403
|
+
f"\\includegraphics[width=0.92\\textwidth]{{{img.name}}}\\\\[1mm]\n"
|
|
404
|
+
f"\\captionof{{figure}}{{{cap}}}\n"
|
|
405
|
+
"\\end{center}\n\n"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
parts.append("\\clearpage\n")
|
|
409
|
+
|
|
410
|
+
parts.append("\\end{document}\n")
|
|
411
|
+
return "".join(parts)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def compile_latex(tex: str, aux_files: dict[str, str]) -> bytes:
|
|
415
|
+
latexmkrc = (
|
|
416
|
+
"$pdf_mode = 1;\n"
|
|
417
|
+
"$pdflatex = 'pdflatex -interaction=nonstopmode -file-line-error %O %S';\n"
|
|
418
|
+
"$latex = 'latex -interaction=nonstopmode -file-line-error %O %S';\n"
|
|
419
|
+
"$xelatex = 'pdflatex -interaction=nonstopmode -file-line-error %O %S';\n"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
payload = {
|
|
423
|
+
"content": tex,
|
|
424
|
+
"main_filename": "main.tex",
|
|
425
|
+
"auxiliary_files": {".latexmkrc": latexmkrc, **aux_files},
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
r = requests.post(API_URL, json=payload, timeout=180)
|
|
429
|
+
r.raise_for_status()
|
|
430
|
+
j = r.json()
|
|
431
|
+
if not j.get("success"):
|
|
432
|
+
raise RuntimeError((j.get("error") or "LaTeX compilation failed") + "\n" + (j.get("log_output") or ""))
|
|
433
|
+
return base64.b64decode(j["pdf_base64"])
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def main():
|
|
437
|
+
ap = argparse.ArgumentParser()
|
|
438
|
+
ap.add_argument("--input", required=True, help="Root folder containing subfolders of images")
|
|
439
|
+
ap.add_argument("--output", required=True, help="Output PDF path")
|
|
440
|
+
ap.add_argument("--title", default="Results", help="Top-level document title")
|
|
441
|
+
|
|
442
|
+
ap.add_argument(
|
|
443
|
+
"--mode",
|
|
444
|
+
choices=["compact", "foldered"],
|
|
445
|
+
default="compact",
|
|
446
|
+
help="compact: Nature-style multi-panel figures (default). foldered: legacy layout.",
|
|
447
|
+
)
|
|
448
|
+
ap.add_argument(
|
|
449
|
+
"--max-per-type",
|
|
450
|
+
type=int,
|
|
451
|
+
default=1,
|
|
452
|
+
help="In compact mode: max images picked per type (type=subfolder). Default: 1.",
|
|
453
|
+
)
|
|
454
|
+
ap.add_argument(
|
|
455
|
+
"--panels-per-figure",
|
|
456
|
+
type=int,
|
|
457
|
+
default=6,
|
|
458
|
+
help="In compact mode: number of subpanels per figure*. Default: 6.",
|
|
459
|
+
)
|
|
460
|
+
ap.add_argument(
|
|
461
|
+
"--cols",
|
|
462
|
+
type=int,
|
|
463
|
+
default=2,
|
|
464
|
+
help="In compact mode: columns per figure*. Default: 2.",
|
|
465
|
+
)
|
|
466
|
+
ap.add_argument(
|
|
467
|
+
"--fig-title",
|
|
468
|
+
default="",
|
|
469
|
+
help="In compact mode: figure caption title (bold). If empty, uses --title.",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
args = ap.parse_args()
|
|
473
|
+
|
|
474
|
+
root = Path(args.input).expanduser().resolve()
|
|
475
|
+
if not root.exists():
|
|
476
|
+
raise SystemExit(f"Input does not exist: {root}")
|
|
477
|
+
|
|
478
|
+
groups = find_groups(root)
|
|
479
|
+
if not groups:
|
|
480
|
+
raise SystemExit(f"No images found under: {root}")
|
|
481
|
+
|
|
482
|
+
# Build selection (compact) or keep all (foldered)
|
|
483
|
+
if args.mode == "compact":
|
|
484
|
+
selected = build_compact_entries(groups, args.max_per_type)
|
|
485
|
+
|
|
486
|
+
if not selected:
|
|
487
|
+
raise SystemExit(f"No images selected under: {root}")
|
|
488
|
+
|
|
489
|
+
# Collect aux files: images at root of LaTeX project need unique, LaTeX-friendly names.
|
|
490
|
+
# We always sanitize filenames to avoid issues with spaces / non-ascii.
|
|
491
|
+
aux: dict[str, str] = {}
|
|
492
|
+
renamed: list[tuple[Path, str]] = []
|
|
493
|
+
used: set[str] = set()
|
|
494
|
+
|
|
495
|
+
def unique_safe_name(group_name: str, img: Path) -> str:
|
|
496
|
+
base = tex_sanitize_filename(img.name)
|
|
497
|
+
# ensure uniqueness across all aux files
|
|
498
|
+
if base not in used:
|
|
499
|
+
return base
|
|
500
|
+
# prefix with group
|
|
501
|
+
pref = tex_sanitize_filename(f"{group_name}__{img.name}")
|
|
502
|
+
if pref not in used:
|
|
503
|
+
return pref
|
|
504
|
+
# final fallback: add numeric suffix
|
|
505
|
+
i = 2
|
|
506
|
+
while True:
|
|
507
|
+
cand = f"{Path(base).stem}_{i}{Path(base).suffix}"
|
|
508
|
+
if cand not in used:
|
|
509
|
+
return cand
|
|
510
|
+
i += 1
|
|
511
|
+
|
|
512
|
+
if args.mode == "compact":
|
|
513
|
+
for group_name, img_list, is_bundle in selected:
|
|
514
|
+
for img in img_list:
|
|
515
|
+
name = unique_safe_name(group_name, img)
|
|
516
|
+
used.add(name)
|
|
517
|
+
aux[name] = b64_file(img)
|
|
518
|
+
renamed.append((img, name))
|
|
519
|
+
|
|
520
|
+
name_map = {orig: new for orig, new in renamed}
|
|
521
|
+
selected2 = [(g, [Path(name_map[p]) for p in img_list], is_bundle) for (g, img_list, is_bundle) in selected]
|
|
522
|
+
|
|
523
|
+
# Sort panels by a preferred type order to stabilize a/b/c...
|
|
524
|
+
preferred = {
|
|
525
|
+
"speed trajectory heatmap": 10,
|
|
526
|
+
"heatmap_trajectory": 10,
|
|
527
|
+
"heatmap_velocity": 20,
|
|
528
|
+
"radar": 30,
|
|
529
|
+
"kinematic parameters": 40,
|
|
530
|
+
"violin": 50,
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
def panel_sort_key(item: tuple[str, list[Path], bool]):
|
|
534
|
+
g, img_list, is_bundle = item
|
|
535
|
+
gl = g.lower()
|
|
536
|
+
k = 999
|
|
537
|
+
for key, pri in preferred.items():
|
|
538
|
+
if gl.startswith(key):
|
|
539
|
+
k = pri
|
|
540
|
+
break
|
|
541
|
+
first_name = img_list[0].name if img_list else ""
|
|
542
|
+
return (k, natural_key(gl), natural_key(first_name))
|
|
543
|
+
|
|
544
|
+
selected2 = sorted(selected2, key=panel_sort_key)
|
|
545
|
+
|
|
546
|
+
tex = build_tex_compact(
|
|
547
|
+
title=args.title,
|
|
548
|
+
panels=selected2,
|
|
549
|
+
fig_title=args.fig_title,
|
|
550
|
+
cols=args.cols,
|
|
551
|
+
panels_per_figure=args.panels_per_figure,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
else:
|
|
555
|
+
for group_name, imgs in groups:
|
|
556
|
+
for img in imgs:
|
|
557
|
+
name = unique_safe_name(group_name, img)
|
|
558
|
+
used.add(name)
|
|
559
|
+
aux[name] = b64_file(img)
|
|
560
|
+
renamed.append((img, name))
|
|
561
|
+
|
|
562
|
+
name_map = {orig: new for orig, new in renamed}
|
|
563
|
+
groups2: list[tuple[str, list[Path]]] = []
|
|
564
|
+
for group_name, imgs in groups:
|
|
565
|
+
imgs2 = [Path(name_map[p]) for p in imgs]
|
|
566
|
+
groups2.append((group_name, imgs2))
|
|
567
|
+
|
|
568
|
+
tex = build_tex_foldered(args.title, groups2)
|
|
569
|
+
|
|
570
|
+
pdf_bytes = compile_latex(tex, aux)
|
|
571
|
+
|
|
572
|
+
out = Path(args.output).expanduser().resolve()
|
|
573
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
574
|
+
out.write_bytes(pdf_bytes)
|
|
575
|
+
print(f"Wrote: {out} ({len(pdf_bytes)} bytes)")
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
if __name__ == "__main__":
|
|
579
|
+
main()
|