@brainpilot/skills 0.0.6 → 0.0.7

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.
Files changed (285) hide show
  1. package/package.json +2 -2
  2. package/skills/01_Meta-Skills/academic-research-hub/SKILL.md +108 -0
  3. package/skills/01_Meta-Skills/academic-research-hub/scripts/requirements.txt +17 -0
  4. package/skills/01_Meta-Skills/academic-research-hub/scripts/research.py +781 -0
  5. package/skills/01_Meta-Skills/beautiful-log/SKILL.md +64 -0
  6. package/skills/01_Meta-Skills/beautiful-log/scripts/beautiful_log.py +274 -0
  7. package/skills/01_Meta-Skills/ethoclaw-daily-paper/SKILL.md +130 -0
  8. package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/config.template.yaml +54 -0
  9. package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/top5_digest_template.md +5 -0
  10. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/build_top5_digest.py +300 -0
  11. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/common.py +137 -0
  12. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/merge_results.py +106 -0
  13. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/run_pipeline.py +177 -0
  14. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_arxiv.py +162 -0
  15. package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_pubmed.py +202 -0
  16. package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/SKILL.md +173 -0
  17. package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/scripts/normalize_data.py +874 -0
  18. package/skills/01_Meta-Skills/ethoclaw-pdf-research/SKILL.md +134 -0
  19. package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/confirmation-prompts.md +31 -0
  20. package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/output-patterns.md +45 -0
  21. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_markdown_deliverables.py +41 -0
  22. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_research_log.py +84 -0
  23. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_summary_md.py +63 -0
  24. package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/extract_pdf_bundle.py +140 -0
  25. package/skills/01_Meta-Skills/experiment-controller/SKILL.md +140 -0
  26. package/skills/01_Meta-Skills/knowledge-graph-builder/SKILL.md +366 -0
  27. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/entity_resolution.py +120 -0
  28. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/extraction_prompt_template.txt +19 -0
  29. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/graph_query.py +106 -0
  30. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/hypothesis_cli_reference.py +42 -0
  31. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/new_data_source_template.py +116 -0
  32. package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/requirements.txt +15 -0
  33. package/skills/01_Meta-Skills/method-design/SKILL.md +61 -0
  34. package/skills/01_Meta-Skills/multi-search-engine/SKILL.md +119 -0
  35. package/skills/01_Meta-Skills/research-idea/SKILL.md +65 -0
  36. package/skills/05_EEG_ERP/eeg-skill/SKILL.md +197 -0
  37. package/skills/05_EEG_ERP/meg-skill/SKILL.md +188 -0
  38. package/skills/05_EEG_ERP/meg-skill/scripts/time_frequency.py +223 -0
  39. package/skills/05_EEG_ERP/mne-eeg-tool/SKILL.md +165 -0
  40. package/skills/05_EEG_ERP/mne-eeg-tool/scripts/eeg_pipeline_reference.py +231 -0
  41. package/skills/05_EEG_ERP/seed-iv-skill/SKILL.md +184 -0
  42. package/skills/05_EEG_ERP/seed-iv-skill/scripts/classify_seed_iv.py +154 -0
  43. package/skills/05_EEG_ERP/seed-iv-skill/scripts/extract_seed_iv_features.py +190 -0
  44. package/skills/05_EEG_ERP/seed-iv-skill/scripts/validate_seed_iv.py +102 -0
  45. package/skills/05_EEG_ERP/seed-vig-skill/SKILL.md +182 -0
  46. package/skills/05_EEG_ERP/seed-vig-skill/scripts/classify_seed_vig.py +165 -0
  47. package/skills/05_EEG_ERP/seed-vig-skill/scripts/extract_seed_vig_features.py +185 -0
  48. package/skills/05_EEG_ERP/seed-vig-skill/scripts/validate_seed_vig.py +88 -0
  49. package/skills/06_fMRI_Neuroimaging/abcd-skill/SKILL.md +308 -0
  50. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/abcd_qc_summary.py +449 -0
  51. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/extract_abcd_phenotype.py +292 -0
  52. package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/reorganize_abcd.py +387 -0
  53. package/skills/06_fMRI_Neuroimaging/abide-skill/SKILL.md +302 -0
  54. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/abide_qc_summary.py +317 -0
  55. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/extract_abide_phenotype.py +267 -0
  56. package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/reorganize_abide.py +387 -0
  57. package/skills/06_fMRI_Neuroimaging/adhd200-skill/SKILL.md +244 -0
  58. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/adhd200_qc_summary.py +98 -0
  59. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/extract_adhd200_phenotype.py +134 -0
  60. package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/reorganize_adhd200.py +206 -0
  61. package/skills/06_fMRI_Neuroimaging/adni-skill/SKILL.md +358 -0
  62. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_adni_task_files.py +1305 -0
  63. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_vqa_from_tasks.py +766 -0
  64. package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/reorganize_adni.py +491 -0
  65. package/skills/06_fMRI_Neuroimaging/aibl-skill/SKILL.md +295 -0
  66. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/aibl_qc_summary.py +260 -0
  67. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/extract_aibl_phenotype.py +365 -0
  68. package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/reorganize_aibl.py +394 -0
  69. package/skills/06_fMRI_Neuroimaging/aomic-skill/SKILL.md +292 -0
  70. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/aomic_qc_summary.py +258 -0
  71. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/extract_aomic_phenotype.py +284 -0
  72. package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/reorganize_aomic.py +322 -0
  73. package/skills/06_fMRI_Neuroimaging/asl-skill/SKILL.md +168 -0
  74. package/skills/06_fMRI_Neuroimaging/asl-skill/scripts/compute_cbf.py +224 -0
  75. package/skills/06_fMRI_Neuroimaging/bids-organizer/SKILL.md +241 -0
  76. package/skills/06_fMRI_Neuroimaging/bold5000-skill/SKILL.md +186 -0
  77. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/bold5000_qc_summary.py +96 -0
  78. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/extract_bold5000_stimulus.py +125 -0
  79. package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/reorganize_bold5000.py +102 -0
  80. package/skills/06_fMRI_Neuroimaging/camcan-skill/SKILL.md +213 -0
  81. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/camcan_qc_summary.py +131 -0
  82. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/extract_camcan_phenotype.py +145 -0
  83. package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/validate_camcan.py +141 -0
  84. package/skills/06_fMRI_Neuroimaging/cobre-skill/SKILL.md +201 -0
  85. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/cobre_qc_summary.py +95 -0
  86. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/extract_cobre_phenotype.py +104 -0
  87. package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/reorganize_cobre.py +140 -0
  88. package/skills/06_fMRI_Neuroimaging/conn-tool/SKILL.md +180 -0
  89. package/skills/06_fMRI_Neuroimaging/dcm2nii/SKILL.md +189 -0
  90. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/SKILL.md +183 -0
  91. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/dmt_har_med_qc_summary.py +96 -0
  92. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/extract_dmt_har_med_phenotype.py +121 -0
  93. package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/reorganize_dmt_har_med.py +125 -0
  94. package/skills/06_fMRI_Neuroimaging/dwi-skill/SKILL.md +359 -0
  95. package/skills/06_fMRI_Neuroimaging/fmri-skill/SKILL.md +371 -0
  96. package/skills/06_fMRI_Neuroimaging/fmriprep-tool/SKILL.md +228 -0
  97. package/skills/06_fMRI_Neuroimaging/freesurfer-tool/SKILL.md +286 -0
  98. package/skills/06_fMRI_Neuroimaging/freesurfer-tool/scripts/freesurfer_processor.py +145 -0
  99. package/skills/06_fMRI_Neuroimaging/fsl-tool/SKILL.md +208 -0
  100. package/skills/06_fMRI_Neuroimaging/hbn-skill/SKILL.md +271 -0
  101. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/extract_hbn_phenotype.py +107 -0
  102. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/hbn_qc_summary.py +96 -0
  103. package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/reorganize_hbn.py +150 -0
  104. package/skills/06_fMRI_Neuroimaging/hcpa-skill/SKILL.md +210 -0
  105. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/extract_hcpa_phenotype.py +146 -0
  106. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/hcpa_qc_summary.py +120 -0
  107. package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/reorganize_hcpa.py +155 -0
  108. package/skills/06_fMRI_Neuroimaging/hcpd-skill/SKILL.md +210 -0
  109. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/extract_hcpd_phenotype.py +148 -0
  110. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/hcpd_qc_summary.py +125 -0
  111. package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/reorganize_hcpd.py +146 -0
  112. package/skills/06_fMRI_Neuroimaging/hcpep-skill/SKILL.md +215 -0
  113. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/extract_hcpep_phenotype.py +157 -0
  114. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/hcpep_qc_summary.py +143 -0
  115. package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/reorganize_hcpep.py +146 -0
  116. package/skills/06_fMRI_Neuroimaging/hcppipeline-tool/SKILL.md +217 -0
  117. package/skills/06_fMRI_Neuroimaging/hcpya-skill/SKILL.md +214 -0
  118. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/extract_hcpya_phenotype.py +190 -0
  119. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/hcpya_qc_summary.py +152 -0
  120. package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/reorganize_hcpya.py +203 -0
  121. package/skills/06_fMRI_Neuroimaging/ixi-skill/SKILL.md +198 -0
  122. package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/ixi_qc_summary.py +137 -0
  123. package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/reorganize_ixi.py +190 -0
  124. package/skills/06_fMRI_Neuroimaging/mnd-skill/SKILL.md +191 -0
  125. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/extract_mnd_phenotype.py +143 -0
  126. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/mnd_qc_summary.py +120 -0
  127. package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/validate_mnd.py +107 -0
  128. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/SKILL.md +203 -0
  129. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/analyze_lesions.py +119 -0
  130. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/longitudinal_lesion.py +148 -0
  131. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/mschallenge_qc_summary.py +132 -0
  132. package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/validate_mschallenge.py +116 -0
  133. package/skills/06_fMRI_Neuroimaging/nibabel-skill/SKILL.md +184 -0
  134. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/atlas_coordinate_reference.py +61 -0
  135. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/freesurfer_io_reference.py +34 -0
  136. package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/nifti_inspection_reference.py +35 -0
  137. package/skills/06_fMRI_Neuroimaging/nifd-skill/SKILL.md +205 -0
  138. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/extract_nifd_phenotype.py +132 -0
  139. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/nifd_qc_summary.py +111 -0
  140. package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/validate_nifd.py +111 -0
  141. package/skills/06_fMRI_Neuroimaging/nii2dcm/SKILL.md +143 -0
  142. package/skills/06_fMRI_Neuroimaging/nilearn-tool/SKILL.md +266 -0
  143. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/connectome_reference.py +65 -0
  144. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/denoise_timeseries_reference.py +58 -0
  145. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/hierarchical_parcellation_reference.py +53 -0
  146. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/kmeans_parcellation_reference.py +53 -0
  147. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/preprocess_bold_reference.py +76 -0
  148. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_dictlearning_reference.py +56 -0
  149. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_ica_reference.py +59 -0
  150. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/second_level_glm_reference.py +58 -0
  151. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/spacenet_classifier_reference.py +59 -0
  152. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/svm_classifier_reference.py +60 -0
  153. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/task_glm_reference.py +63 -0
  154. package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/zalff_summary_reference.py +109 -0
  155. package/skills/06_fMRI_Neuroimaging/nsd-skill/SKILL.md +210 -0
  156. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/extract_nsd_stimulus.py +171 -0
  157. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/nsd_qc_summary.py +142 -0
  158. package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/validate_nsd.py +142 -0
  159. package/skills/06_fMRI_Neuroimaging/oasis-skill/SKILL.md +205 -0
  160. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/extract_oasis_phenotype.py +126 -0
  161. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/oasis_qc_summary.py +115 -0
  162. package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/validate_oasis.py +119 -0
  163. package/skills/06_fMRI_Neuroimaging/pet-skill/SKILL.md +173 -0
  164. package/skills/06_fMRI_Neuroimaging/pet-skill/scripts/compute_suvr.py +202 -0
  165. package/skills/06_fMRI_Neuroimaging/pnc-skill/SKILL.md +206 -0
  166. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/extract_pnc_phenotype.py +136 -0
  167. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/pnc_qc_summary.py +116 -0
  168. package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/validate_pnc.py +120 -0
  169. package/skills/06_fMRI_Neuroimaging/ppmi-skill/SKILL.md +209 -0
  170. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/extract_ppmi_phenotype.py +138 -0
  171. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/ppmi_qc_summary.py +111 -0
  172. package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/validate_ppmi.py +117 -0
  173. package/skills/06_fMRI_Neuroimaging/qsiprep-tool/SKILL.md +320 -0
  174. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/SKILL.md +215 -0
  175. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/extract_rest_mdd_phenotype.py +132 -0
  176. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/harmonize_sites.py +152 -0
  177. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/rest_mdd_qc_summary.py +124 -0
  178. package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/validate_rest_mdd.py +103 -0
  179. package/skills/06_fMRI_Neuroimaging/smri-skill/SKILL.md +302 -0
  180. package/skills/06_fMRI_Neuroimaging/tcp-skill/SKILL.md +204 -0
  181. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/extract_tcp_phenotype.py +139 -0
  182. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/tcp_qc_summary.py +111 -0
  183. package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/validate_tcp.py +99 -0
  184. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/SKILL.md +217 -0
  185. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/extract_ucla_cnp_phenotype.py +145 -0
  186. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/ucla_cnp_qc_summary.py +111 -0
  187. package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/validate_ucla_cnp.py +113 -0
  188. package/skills/06_fMRI_Neuroimaging/ukb-skill/SKILL.md +310 -0
  189. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/build_ukb_survival.py +210 -0
  190. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_cases.py +308 -0
  191. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_phenotype.py +232 -0
  192. package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/ukb_qc_summary.py +158 -0
  193. package/skills/06_fMRI_Neuroimaging/wmh-segmentation/SKILL.md +133 -0
  194. package/skills/07_Computational_Modeling/detrending/SKILL.md +118 -0
  195. package/skills/07_Computational_Modeling/dictlearning/SKILL.md +122 -0
  196. package/skills/07_Computational_Modeling/filtering/SKILL.md +121 -0
  197. package/skills/07_Computational_Modeling/glm/SKILL.md +153 -0
  198. package/skills/07_Computational_Modeling/hierarchical/SKILL.md +121 -0
  199. package/skills/07_Computational_Modeling/ica/SKILL.md +122 -0
  200. package/skills/07_Computational_Modeling/kmeans/SKILL.md +119 -0
  201. package/skills/07_Computational_Modeling/run_models/SKILL.md +427 -0
  202. package/skills/07_Computational_Modeling/spacenet/SKILL.md +122 -0
  203. package/skills/07_Computational_Modeling/svm/SKILL.md +120 -0
  204. package/skills/08_Computational_Neuroscience/brain_gnn/SKILL.md +183 -0
  205. package/skills/08_Computational_Neuroscience/dipy-tool/SKILL.md +239 -0
  206. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/dti_metrics_reference.py +70 -0
  207. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/load_and_mask_reference.py +76 -0
  208. package/skills/08_Computational_Neuroscience/dipy-tool/scripts/roi_stats_reference.py +59 -0
  209. package/skills/08_Computational_Neuroscience/fm_app/SKILL.md +195 -0
  210. package/skills/08_Computational_Neuroscience/neurostorm/SKILL.md +151 -0
  211. package/skills/13_Visualization/brain-visualization/SKILL.md +191 -0
  212. package/skills/13_Visualization/brain-visualization/scripts/connectome_reference.py +108 -0
  213. package/skills/13_Visualization/brain-visualization/scripts/freesurfer_ply_reference.py +54 -0
  214. package/skills/13_Visualization/brain-visualization/scripts/zalff_summary_reference.py +116 -0
  215. package/skills/13_Visualization/ethoclaw-paper-figure-layout/SKILL.md +78 -0
  216. package/skills/13_Visualization/ethoclaw-paper-figure-layout/assets/naturecomm_figures.tex +74 -0
  217. package/skills/13_Visualization/ethoclaw-paper-figure-layout/scripts/layout_results_foldered.py +579 -0
  218. package/skills/14_Writing/overleaf-skill/SKILL.md +184 -0
  219. package/skills/14_Writing/overleaf-skill/scripts/install.sh +30 -0
  220. package/skills/14_Writing/paper-writing/SKILL.md +146 -0
  221. package/skills/14_Writing/paper-writing/scripts/data_statement_templates.py +164 -0
  222. package/skills/14_Writing/paper-writing/scripts/figure_templates.py +315 -0
  223. package/skills/14_Writing/paper-writing/scripts/nature_figure_style.py +214 -0
  224. package/skills/14_Writing/paper-writing/scripts/section_phrasebank.py +246 -0
  225. package/skills/16_Animal_Behavior/deeplabcut/SKILL.md +154 -0
  226. package/skills/16_Animal_Behavior/deeplabcut/references/3d-pose.md +89 -0
  227. package/skills/16_Animal_Behavior/deeplabcut/references/maDLC.md +123 -0
  228. package/skills/16_Animal_Behavior/deeplabcut/references/modelzoo.md +98 -0
  229. package/skills/16_Animal_Behavior/deeplabcut/references/standard-pipeline.md +165 -0
  230. package/skills/16_Animal_Behavior/deeplabcut/references/utilities.md +146 -0
  231. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/SKILL.md +274 -0
  232. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.html +112 -0
  233. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.md +21 -0
  234. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/cluster-section.md +5 -0
  235. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/heatmap-section.md +5 -0
  236. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/integrated-interpretation.md +3 -0
  237. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/overview.md +3 -0
  238. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/project-summary.md +3 -0
  239. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/radar-section.md +5 -0
  240. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/raw-trajectory.md +3 -0
  241. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/sample-check.md +3 -0
  242. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/single-subject-section.md +3 -0
  243. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/stats-section.md +5 -0
  244. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/epm.md +52 -0
  245. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/fst.md +37 -0
  246. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/nor.md +39 -0
  247. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/oft.md +43 -0
  248. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tcst.md +45 -0
  249. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tst.md +36 -0
  250. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/input-types.md +59 -0
  251. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/interpretation-guardrails.md +45 -0
  252. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/metadata-schema.md +57 -0
  253. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/report-sections.md +86 -0
  254. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/section-selection-rules.md +169 -0
  255. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/build_report_manifest.py +27 -0
  256. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/render_report.py +34 -0
  257. package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/report_utils.py +1121 -0
  258. package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/SKILL.md +390 -0
  259. package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/reference_code.py +98 -0
  260. package/skills/16_Animal_Behavior/ethoclaw-animal-pose-estimation/SKILL.md +336 -0
  261. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/README.md +21 -0
  262. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/SKILL.md +41 -0
  263. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/batch_kinematic_generator.py +663 -0
  264. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/config.json +19 -0
  265. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/generate_kinematic_parameter.py +401 -0
  266. package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/kinematic_generator.py +265 -0
  267. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/SKILL.md +72 -0
  268. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/references/config.example.toml +56 -0
  269. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params.py +232 -0
  270. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params_from_config.py +236 -0
  271. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/SKILL.md +68 -0
  272. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/references/notes.md +5 -0
  273. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/scripts/plot_h5_radar.py +513 -0
  274. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/SKILL.md +52 -0
  275. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/config.toml +81 -0
  276. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/references/stats-rule.md +18 -0
  277. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_inspect.py +79 -0
  278. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_batch.py +624 -0
  279. package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_stats.py +438 -0
  280. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/SKILL.md +280 -0
  281. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_trajectory.py +790 -0
  282. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_velocity.py +855 -0
  283. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.csv +101 -0
  284. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.h5 +0 -0
  285. package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_data_readme.md +126 -0
@@ -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()