@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.
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,855 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Velocity Heatmap Generator for Trajectory Velocity Heatmap Generate Skill
4
+
5
+ Generates 2D spatial velocity heatmaps from 2D/3D tracking data (.h5 or .csv).
6
+ Shows which spatial regions have the highest movement velocity.
7
+ Supports flexible data formats, auto-detection of body parts, and robust error handling.
8
+ """
9
+
10
+ import argparse
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Tuple, Any
15
+
16
+ import numpy as np
17
+ import pandas as pd
18
+ import matplotlib
19
+ matplotlib.use('Agg')
20
+ import matplotlib.pyplot as plt
21
+ from scipy.ndimage import gaussian_filter
22
+ from matplotlib.collections import LineCollection
23
+
24
+
25
+ def parse_args() -> argparse.Namespace:
26
+ parser = argparse.ArgumentParser(
27
+ description="Generate spatial velocity heatmaps from animal tracking data"
28
+ )
29
+ parser.add_argument(
30
+ "input_path",
31
+ type=str,
32
+ help="Input file (.h5 or .csv) or directory"
33
+ )
34
+ parser.add_argument(
35
+ "--body-part",
36
+ type=str,
37
+ default="auto",
38
+ help="Body part to analyze (default: auto-detect)"
39
+ )
40
+ parser.add_argument(
41
+ "--list-parts",
42
+ action="store_true",
43
+ help="List available body parts and exit"
44
+ )
45
+ parser.add_argument(
46
+ "--confidence-threshold",
47
+ type=float,
48
+ default=0.6,
49
+ help="Minimum confidence threshold (default: 0.6)"
50
+ )
51
+ parser.add_argument(
52
+ "--fps",
53
+ type=float,
54
+ default=30.0,
55
+ help="Video frame rate (default: 30)"
56
+ )
57
+ parser.add_argument(
58
+ "--output-dir",
59
+ type=str,
60
+ default=None,
61
+ help="Output directory (default: auto)"
62
+ )
63
+ parser.add_argument(
64
+ "--cmap",
65
+ type=str,
66
+ default="viridis",
67
+ help="Matplotlib colormap (default: viridis)"
68
+ )
69
+ parser.add_argument(
70
+ "--bins",
71
+ type=int,
72
+ default=50,
73
+ help="Number of bins for 2D histogram (default: 50)"
74
+ )
75
+ parser.add_argument(
76
+ "--sigma",
77
+ type=float,
78
+ default=2.0,
79
+ help="Gaussian smoothing sigma (default: 2.0)"
80
+ )
81
+ parser.add_argument(
82
+ "--arena-size",
83
+ type=str,
84
+ default=None,
85
+ help="Arena size in pixels (format: width,height). Auto-detected if not specified."
86
+ )
87
+ parser.add_argument(
88
+ "--outlier-threshold",
89
+ type=float,
90
+ default=10.0,
91
+ help="Outlier IQR multiplier (0 to disable, default: 10)"
92
+ )
93
+ parser.add_argument(
94
+ "--velocity-max",
95
+ type=float,
96
+ default=None,
97
+ help="Maximum velocity for color scale (auto-detected if not specified)"
98
+ )
99
+ return parser.parse_args()
100
+
101
+
102
+ def detect_body_parts(df: pd.DataFrame) -> List[str]:
103
+ """
104
+ Auto-detect body parts from DataFrame columns.
105
+ Supports various naming patterns.
106
+ """
107
+ body_parts = set()
108
+ columns = [c.lower() for c in df.columns]
109
+
110
+ # Pattern 1: {part}_x, {part}_y, {part}_confidence
111
+ pattern1 = re.compile(r'^(.+)_(x|y|z|confidence|likelihood|score)$')
112
+
113
+ # Pattern 2: x_{part}, y_{part}, confidence_{part}
114
+ pattern2 = re.compile(r'^(x|y|z|confidence|likelihood|score)_(.+)$')
115
+
116
+ for col in columns:
117
+ # Pattern 1
118
+ match = pattern1.match(col)
119
+ if match:
120
+ part = match.group(1)
121
+ if part not in ['', 'x', 'y', 'z']:
122
+ body_parts.add(part)
123
+
124
+ # Pattern 2
125
+ match = pattern2.match(col)
126
+ if match:
127
+ part = match.group(2)
128
+ body_parts.add(part)
129
+
130
+ # Additional check: look for pairs of x,y columns
131
+ for col in columns:
132
+ if col.endswith('_x') or col.endswith('x'):
133
+ base = col[:-1] if col.endswith('x') else col[:-2]
134
+ y_col = base + '_y' if col.endswith('_x') else base + 'y'
135
+ if y_col in columns or (base + '_y') in columns:
136
+ body_parts.add(base.rstrip('_'))
137
+
138
+ return sorted(list(body_parts))
139
+
140
+
141
+ def get_column_names(df: pd.DataFrame, body_part: str) -> Dict[str, str]:
142
+ """
143
+ Get actual column names for a body part (handles case variations).
144
+ Returns dict with keys: x, y, confidence
145
+ """
146
+ columns = list(df.columns)
147
+ columns_lower = [c.lower() for c in columns]
148
+ result = {}
149
+
150
+ # Try various patterns
151
+ patterns = [
152
+ (f"{body_part}_x", f"{body_part}_y", f"{body_part}_confidence"),
153
+ (f"{body_part}_x", f"{body_part}_y", f"{body_part}_likelihood"),
154
+ (f"{body_part}_x", f"{body_part}_y", f"{body_part}_score"),
155
+ (f"x_{body_part}", f"y_{body_part}", f"confidence_{body_part}"),
156
+ (f"x_{body_part}", f"y_{body_part}", f"likelihood_{body_part}"),
157
+ (f"{body_part}x", f"{body_part}y", f"{body_part}confidence"),
158
+ (f"{body_part}X", f"{body_part}Y", f"{body_part}Confidence"),
159
+ ]
160
+
161
+ for x_pat, y_pat, conf_pat in patterns:
162
+ x_col = None
163
+ y_col = None
164
+ conf_col = None
165
+
166
+ for i, col_lower in enumerate(columns_lower):
167
+ if col_lower == x_pat.lower():
168
+ x_col = columns[i]
169
+ elif col_lower == y_pat.lower():
170
+ y_col = columns[i]
171
+ elif col_lower == conf_pat.lower():
172
+ conf_col = columns[i]
173
+
174
+ if x_col and y_col:
175
+ result['x'] = x_col
176
+ result['y'] = y_col
177
+ result['confidence'] = conf_col
178
+ return result
179
+
180
+ # Fallback: search for any column containing body_part and x/y
181
+ for i, col in enumerate(columns):
182
+ col_lower = col.lower()
183
+ if body_part.lower() in col_lower:
184
+ if 'x' in col_lower and 'y' not in col_lower and 'z' not in col_lower:
185
+ result['x'] = col
186
+ elif 'y' in col_lower and 'x' not in col_lower and 'z' not in col_lower:
187
+ result['y'] = col
188
+ elif any(c in col_lower for c in ['confidence', 'likelihood', 'score']):
189
+ result['confidence'] = col
190
+
191
+ return result if 'x' in result and 'y' in result else {}
192
+
193
+
194
+ def load_h5_data(h5_path: Path) -> pd.DataFrame:
195
+ """Load tracking data from HDF5 file with flexible structure detection."""
196
+ import h5py
197
+
198
+ with h5py.File(h5_path, 'r') as f:
199
+ # Try different common structures
200
+ structures = [
201
+ ('/2Dskeleton/BodyParts', '/2Dskeleton/data2D'),
202
+ ('/body_parts', '/coordinates'),
203
+ ('/keypoints', '/data'),
204
+ ('/bodyparts', '/positions'),
205
+ ]
206
+
207
+ body_parts = None
208
+ data = None
209
+
210
+ for parts_path, data_path in structures:
211
+ if parts_path in f and data_path in f:
212
+ body_parts = f[parts_path][:]
213
+ data = f[data_path][:]
214
+ break
215
+
216
+ # If not found, try to auto-detect
217
+ if body_parts is None:
218
+ for key in f.keys():
219
+ if isinstance(f[key], h5py.Group):
220
+ for subkey in f[key].keys():
221
+ if 'part' in subkey.lower() or 'bodypart' in subkey.lower():
222
+ parts_path = f"{key}/{subkey}"
223
+ if parts_path in f:
224
+ body_parts = f[parts_path][:]
225
+ for data_key in ['data2D', 'data', 'coordinates', 'positions']:
226
+ data_path = f"{key}/{data_key}"
227
+ if data_path in f:
228
+ data = f[data_path][:]
229
+ break
230
+ if data is not None:
231
+ break
232
+ if body_parts is not None:
233
+ break
234
+
235
+ if body_parts is None or data is None:
236
+ raise ValueError("Could not detect HDF5 structure. Expected body parts and coordinate datasets.")
237
+
238
+ # Decode body parts if needed
239
+ decoded_parts = []
240
+ for part in body_parts:
241
+ if isinstance(part, bytes):
242
+ decoded_parts.append(part.decode('utf-8'))
243
+ else:
244
+ decoded_parts.append(str(part))
245
+
246
+ # Build column names
247
+ column_names = []
248
+ for part in decoded_parts:
249
+ column_names.extend([f"{part}_x", f"{part}_y", f"{part}_confidence"])
250
+
251
+ # Ensure data shape matches
252
+ expected_cols = len(decoded_parts) * 3
253
+ if data.shape[1] != expected_cols:
254
+ if data.shape[1] == len(decoded_parts) * 2:
255
+ column_names = []
256
+ for part in decoded_parts:
257
+ column_names.extend([f"{part}_x", f"{part}_y"])
258
+ else:
259
+ column_names = [f"col_{i}" for i in range(data.shape[1])]
260
+
261
+ df = pd.DataFrame(data, columns=column_names)
262
+ return df
263
+
264
+
265
+ def load_csv_data(csv_path: Path) -> pd.DataFrame:
266
+ """Load tracking data from CSV file with flexible parsing."""
267
+ try:
268
+ df = pd.read_csv(csv_path)
269
+ except Exception:
270
+ try:
271
+ df = pd.read_csv(csv_path, sep=';')
272
+ except Exception:
273
+ df = pd.read_csv(csv_path, sep='\t')
274
+
275
+ df.columns = [str(c).strip() for c in df.columns]
276
+ return df
277
+
278
+
279
+ def load_tracking_data(input_path: Path) -> pd.DataFrame:
280
+ """Load tracking data from either .h5 or .csv file."""
281
+ suffix = input_path.suffix.lower()
282
+
283
+ if suffix == '.h5' or suffix == '.hdf5':
284
+ return load_h5_data(input_path)
285
+ elif suffix == '.csv' or suffix == '.txt':
286
+ return load_csv_data(input_path)
287
+ else:
288
+ raise ValueError(f"Unsupported file format: {suffix}")
289
+
290
+
291
+ def clean_data(df: pd.DataFrame, x_col: str, y_col: str, conf_col: Optional[str] = None, outlier_threshold: float = 10.0) -> Tuple[pd.Series, pd.Series, pd.Series]:
292
+ """
293
+ Clean data by handling missing values, outliers, and invalid coordinates.
294
+ """
295
+ x = pd.to_numeric(df[x_col], errors='coerce').copy()
296
+ y = pd.to_numeric(df[y_col], errors='coerce').copy()
297
+
298
+ # Handle confidence
299
+ if conf_col and conf_col in df.columns:
300
+ confidence = pd.to_numeric(df[conf_col], errors='coerce').fillna(0)
301
+ else:
302
+ confidence = pd.Series(np.ones(len(df)))
303
+
304
+ # Replace inf with nan
305
+ x = x.replace([np.inf, -np.inf], np.nan)
306
+ y = y.replace([np.inf, -np.inf], np.nan)
307
+
308
+ # Detect and mark extreme outliers
309
+ if outlier_threshold > 0:
310
+ for coord in [x, y]:
311
+ valid = coord.dropna()
312
+ if len(valid) > 10:
313
+ q1 = valid.quantile(0.25)
314
+ q3 = valid.quantile(0.75)
315
+ iqr = q3 - q1
316
+ lower = q1 - outlier_threshold * iqr
317
+ upper = q3 + outlier_threshold * iqr
318
+ coord[(coord < lower) | (coord > upper)] = np.nan
319
+
320
+ return x, y, confidence
321
+
322
+
323
+ def calculate_velocity(x: pd.Series, y: pd.Series, fps: float) -> pd.Series:
324
+ """
325
+ Calculate instantaneous velocity from position data.
326
+ """
327
+ # Calculate differences
328
+ dx = x.diff()
329
+ dy = y.diff()
330
+ dt = 1.0 / fps
331
+
332
+ # Calculate velocity (pixels per second)
333
+ velocity = np.sqrt(dx**2 + dy**2) / dt
334
+
335
+ return velocity
336
+
337
+
338
+ def calculate_velocity_heatmap(
339
+ x: np.ndarray,
340
+ y: np.ndarray,
341
+ velocity: np.ndarray,
342
+ confidence: np.ndarray,
343
+ confidence_threshold: float,
344
+ bins: int,
345
+ sigma: float,
346
+ arena_size: Optional[Tuple[int, int]] = None
347
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
348
+ """
349
+ Calculate 2D spatial velocity heatmap.
350
+ Shows which spatial regions have the highest movement velocity.
351
+ """
352
+ # Create valid mask
353
+ valid_mask = (~np.isnan(x)) & (~np.isnan(y)) & (~np.isnan(velocity)) & (confidence >= confidence_threshold)
354
+ x_valid = x[valid_mask]
355
+ y_valid = y[valid_mask]
356
+ velocity_valid = velocity[valid_mask]
357
+
358
+ if len(x_valid) < 10:
359
+ raise ValueError("Insufficient valid data points for heatmap generation")
360
+
361
+ # Determine range
362
+ if arena_size is not None:
363
+ x_range = (0, arena_size[0])
364
+ y_range = (0, arena_size[1])
365
+ else:
366
+ x_padding = (x_valid.max() - x_valid.min()) * 0.05
367
+ y_padding = (y_valid.max() - y_valid.min()) * 0.05
368
+ x_range = (x_valid.min() - x_padding, x_valid.max() + x_padding)
369
+ y_range = (y_valid.min() - y_padding, y_valid.max() + y_padding)
370
+
371
+ # Create 2D histogram weighted by velocity
372
+ # Using np.histogram2d with weights to get sum of velocities per bin
373
+ heatmap_sum, xedges, yedges = np.histogram2d(
374
+ x_valid, y_valid,
375
+ bins=bins,
376
+ range=[x_range, y_range],
377
+ weights=velocity_valid
378
+ )
379
+
380
+ # Create count histogram to get average velocity per bin
381
+ heatmap_count, _, _ = np.histogram2d(
382
+ x_valid, y_valid,
383
+ bins=bins,
384
+ range=[x_range, y_range]
385
+ )
386
+
387
+ # Calculate average velocity (avoid division by zero)
388
+ with np.errstate(divide='ignore', invalid='ignore'):
389
+ heatmap = np.divide(heatmap_sum, heatmap_count)
390
+ heatmap = np.nan_to_num(heatmap, nan=0.0)
391
+
392
+ # Apply Gaussian smoothing
393
+ if sigma > 0:
394
+ heatmap = gaussian_filter(heatmap, sigma=sigma)
395
+
396
+ return heatmap, xedges, yedges
397
+
398
+
399
+ def generate_velocity_heatmap_figure(
400
+ heatmap: np.ndarray,
401
+ xedges: np.ndarray,
402
+ yedges: np.ndarray,
403
+ x: np.ndarray,
404
+ y: np.ndarray,
405
+ velocity: np.ndarray,
406
+ confidence: np.ndarray,
407
+ confidence_threshold: float,
408
+ title: str,
409
+ cmap: str,
410
+ body_part: str,
411
+ velocity_max: Optional[float] = None
412
+ ) -> plt.Figure:
413
+ """Generate spatial velocity heatmap visualization."""
414
+
415
+ fig, axes = plt.subplots(1, 2, figsize=(16, 7))
416
+
417
+ extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
418
+
419
+ # Determine color scale
420
+ if velocity_max is None:
421
+ if np.any(heatmap > 0):
422
+ # Use 99th percentile to avoid outliers skewing the color scale
423
+ vmax = np.percentile(heatmap[heatmap > 0], 99)
424
+ else:
425
+ vmax = 1.0
426
+ else:
427
+ vmax = velocity_max
428
+ vmax = max(vmax, 0.1)
429
+
430
+ # Left panel: Velocity heatmap only
431
+ ax1 = axes[0]
432
+
433
+ im1 = ax1.imshow(
434
+ heatmap.T,
435
+ origin='lower',
436
+ extent=extent,
437
+ cmap=cmap,
438
+ aspect='auto',
439
+ vmin=0,
440
+ vmax=vmax
441
+ )
442
+ ax1.set_xlabel('X Position (pixels)')
443
+ ax1.set_ylabel('Y Position (pixels)')
444
+ ax1.set_title(f'{title}\nSpatial Velocity Heatmap ({body_part})')
445
+
446
+ cbar1 = plt.colorbar(im1, ax=ax1)
447
+ cbar1.set_label('Average Velocity (pixels/sec)')
448
+
449
+ # Right panel: Velocity heatmap with trajectory overlay
450
+ ax2 = axes[1]
451
+
452
+ im2 = ax2.imshow(
453
+ heatmap.T,
454
+ origin='lower',
455
+ extent=extent,
456
+ cmap=cmap,
457
+ aspect='auto',
458
+ alpha=0.7,
459
+ vmin=0,
460
+ vmax=vmax
461
+ )
462
+
463
+ # Overlay trajectory line colored by velocity
464
+ valid_mask = (~np.isnan(x)) & (~np.isnan(y)) & (~np.isnan(velocity)) & (confidence >= confidence_threshold)
465
+ x_valid = x[valid_mask]
466
+ y_valid = y[valid_mask]
467
+ velocity_valid = velocity[valid_mask]
468
+
469
+ # Downsample for visualization if too many points
470
+ if len(x_valid) > 5000:
471
+ step = len(x_valid) // 5000
472
+ indices = np.arange(0, len(x_valid), step)
473
+ x_plot = x_valid[indices]
474
+ y_plot = y_valid[indices]
475
+ velocity_plot = velocity_valid[indices]
476
+ else:
477
+ x_plot = x_valid
478
+ y_plot = y_valid
479
+ velocity_plot = velocity_valid
480
+
481
+ # Plot trajectory with velocity-based coloring
482
+ if len(x_plot) > 1:
483
+ points = np.array([x_plot, y_plot]).T.reshape(-1, 1, 2)
484
+ segments = np.concatenate([points[:-1], points[1:]], axis=1)
485
+
486
+ # Normalize velocity for coloring
487
+ vel_norm = np.clip(velocity_plot[:-1] / vmax, 0, 1)
488
+ lc = LineCollection(segments, cmap=cmap, alpha=0.6)
489
+ lc.set_array(vel_norm)
490
+ lc.set_linewidth(1)
491
+ ax2.add_collection(lc)
492
+
493
+ ax2.set_xlim(extent[0], extent[1])
494
+ ax2.set_ylim(extent[2], extent[3])
495
+ ax2.set_xlabel('X Position (pixels)')
496
+ ax2.set_ylabel('Y Position (pixels)')
497
+ ax2.set_title(f'{title}\nVelocity Heatmap + Trajectory ({body_part})')
498
+
499
+ cbar2 = plt.colorbar(im2, ax=ax2)
500
+ cbar2.set_label('Average Velocity (pixels/sec)')
501
+
502
+ plt.tight_layout()
503
+ return fig
504
+
505
+
506
+ def detect_group(filename: str) -> str:
507
+ """Detect group from filename."""
508
+ parts = filename.replace('.h5', '').replace('.csv', '').replace('.hdf5', '').split('_')
509
+ return parts[0] if parts else "unknown"
510
+
511
+
512
+ def parse_arena_size(arena_size_str: Optional[str]) -> Optional[Tuple[int, int]]:
513
+ """Parse arena size string into (width, height) tuple."""
514
+ if arena_size_str is None:
515
+ return None
516
+ try:
517
+ width, height = map(int, arena_size_str.split(','))
518
+ return (width, height)
519
+ except ValueError:
520
+ print(f"Warning: Invalid arena size format: {arena_size_str}. Using auto-detection.")
521
+ return None
522
+
523
+
524
+ def get_output_dir(input_path: Path, custom_output_dir: Optional[str] = None) -> Path:
525
+ """Determine output directory."""
526
+ if custom_output_dir:
527
+ return Path(custom_output_dir)
528
+
529
+ if input_path.is_dir():
530
+ data_dir = input_path
531
+ else:
532
+ data_dir = input_path.parent
533
+
534
+ if data_dir.name == "1_2Dskeleton":
535
+ project_root = data_dir.parent
536
+ results_dir = project_root / "2_results" / "heatmap_velocity"
537
+ else:
538
+ results_dir = data_dir / "results" / "heatmap_velocity"
539
+
540
+ return results_dir
541
+
542
+
543
+ def process_single_file(
544
+ input_file: Path,
545
+ body_part: str,
546
+ confidence_threshold: float,
547
+ fps: float,
548
+ output_dir: Path,
549
+ cmap: str,
550
+ bins: int,
551
+ sigma: float,
552
+ arena_size: Optional[Tuple[int, int]],
553
+ outlier_threshold: float,
554
+ velocity_max: Optional[float],
555
+ list_only: bool = False
556
+ ) -> Optional[List[str]]:
557
+ """Process a single tracking file."""
558
+
559
+ print(f"Processing: {input_file.name}")
560
+
561
+ # Load data
562
+ try:
563
+ df = load_tracking_data(input_file)
564
+ except Exception as e:
565
+ print(f" Error loading file: {e}")
566
+ return None
567
+
568
+ print(f" Loaded {len(df)} frames, {len(df.columns)} columns")
569
+
570
+ # Detect body parts
571
+ available_parts = detect_body_parts(df)
572
+
573
+ if list_only:
574
+ return available_parts
575
+
576
+ if not available_parts:
577
+ print(f" Error: No body parts detected in columns: {list(df.columns)}")
578
+ return None
579
+
580
+ print(f" Detected body parts: {', '.join(available_parts)}")
581
+
582
+ # Select body part
583
+ if body_part == "auto":
584
+ preferred = ['center', 'body', 'back', 'midpoint', 'nose', 'head']
585
+ selected = None
586
+ for pref in preferred:
587
+ matches = [p for p in available_parts if pref in p.lower()]
588
+ if matches:
589
+ selected = matches[0]
590
+ break
591
+ if not selected:
592
+ selected = available_parts[0]
593
+ body_part = selected
594
+ print(f" Auto-selected body part: {body_part}")
595
+ else:
596
+ matches = [p for p in available_parts if p.lower() == body_part.lower()]
597
+ if not matches:
598
+ print(f" Warning: Body part '{body_part}' not found. Using: {available_parts[0]}")
599
+ body_part = available_parts[0]
600
+ else:
601
+ body_part = matches[0]
602
+
603
+ # Get column names
604
+ col_names = get_column_names(df, body_part)
605
+ if not col_names or 'x' not in col_names or 'y' not in col_names:
606
+ print(f" Error: Could not find coordinate columns for '{body_part}'")
607
+ print(f" Available columns: {list(df.columns)}")
608
+ return None
609
+
610
+ print(f" Using columns: x='{col_names['x']}', y='{col_names['y']}'", end="")
611
+ if 'confidence' in col_names:
612
+ print(f", confidence='{col_names['confidence']}'")
613
+ else:
614
+ print(" (no confidence column)")
615
+
616
+ # Clean data
617
+ x, y, confidence = clean_data(df, col_names['x'], col_names['y'], col_names.get('confidence'), outlier_threshold)
618
+
619
+ valid_count = (~x.isna() & ~y.isna() & (confidence >= confidence_threshold)).sum()
620
+ print(f" Valid frames: {valid_count}/{len(df)} ({100*valid_count/len(df):.1f}%)")
621
+
622
+ if valid_count < 10:
623
+ print(f" Skipping: insufficient valid data")
624
+ return None
625
+
626
+ # Calculate velocity
627
+ velocity = calculate_velocity(x, y, fps)
628
+
629
+ # Calculate velocity heatmap
630
+ try:
631
+ heatmap, xedges, yedges = calculate_velocity_heatmap(
632
+ x.values, y.values, velocity.values, confidence.values,
633
+ confidence_threshold, bins, sigma, arena_size
634
+ )
635
+ except ValueError as e:
636
+ print(f" Error: {e}")
637
+ return None
638
+ except Exception as e:
639
+ print(f" Error calculating heatmap: {e}")
640
+ return None
641
+
642
+ # Generate visualization
643
+ group = detect_group(input_file.stem)
644
+ title = f"{input_file.stem} ({group})"
645
+
646
+ try:
647
+ fig = generate_velocity_heatmap_figure(
648
+ heatmap, xedges, yedges, x.values, y.values, velocity.values, confidence.values,
649
+ confidence_threshold, title, cmap, body_part, velocity_max
650
+ )
651
+ except Exception as e:
652
+ print(f" Error generating plot: {e}")
653
+ return None
654
+
655
+ # Save output
656
+ output_dir.mkdir(parents=True, exist_ok=True)
657
+ output_file = output_dir / f"{input_file.stem}_velocity.png"
658
+
659
+ try:
660
+ fig.savefig(output_file, dpi=150, bbox_inches='tight')
661
+ plt.close(fig)
662
+ print(f" Saved: {output_file}")
663
+ except Exception as e:
664
+ print(f" Error saving file: {e}")
665
+ plt.close(fig)
666
+ return None
667
+
668
+ # Statistics
669
+ valid_velocity = velocity[(~x.isna()) & (~y.isna()) & (confidence >= confidence_threshold)]
670
+ if len(valid_velocity) > 0 and not valid_velocity.isna().all():
671
+ print(f" Velocity stats: mean={valid_velocity.mean():.2f}, "
672
+ f"median={valid_velocity.median():.2f}, "
673
+ f"max={valid_velocity.max():.2f} pixels/sec")
674
+
675
+ # Find hotspot (region with highest average velocity)
676
+ if np.any(heatmap > 0):
677
+ max_idx = np.unravel_index(np.argmax(heatmap), heatmap.shape)
678
+ hotspot_x = xedges[max_idx[0]] + (xedges[1] - xedges[0]) / 2
679
+ hotspot_y = yedges[max_idx[1]] + (yedges[1] - yedges[0]) / 2
680
+ print(f" Velocity hotspot: ({hotspot_x:.1f}, {hotspot_y:.1f}) "
681
+ f"with avg velocity {heatmap[max_idx]:.2f} pixels/sec")
682
+
683
+ return available_parts
684
+
685
+
686
+ def calculate_file_heatmap(
687
+ input_file: Path,
688
+ body_part: str,
689
+ confidence_threshold: float,
690
+ fps: float,
691
+ bins: int,
692
+ sigma: float,
693
+ arena_size: Optional[Tuple[int, int]],
694
+ outlier_threshold: float
695
+ ) -> Tuple[Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[str]]:
696
+ """Calculate velocity heatmap for a single file without generating visualization.
697
+ Returns (heatmap, xedges, yedges, selected_body_part) or (None, None, None, None) on error.
698
+ """
699
+ try:
700
+ df = load_tracking_data(input_file)
701
+ except Exception as e:
702
+ print(f" Error loading file: {e}")
703
+ return None, None, None, None
704
+
705
+ # Detect body parts
706
+ available_parts = detect_body_parts(df)
707
+ if not available_parts:
708
+ return None, None, None, None
709
+
710
+ # Select body part
711
+ if body_part == "auto":
712
+ preferred = ['center', 'body', 'back', 'midpoint', 'nose', 'head']
713
+ selected = None
714
+ for pref in preferred:
715
+ matches = [p for p in available_parts if pref in p.lower()]
716
+ if matches:
717
+ selected = matches[0]
718
+ break
719
+ if not selected:
720
+ selected = available_parts[0]
721
+ selected_body_part = selected
722
+ else:
723
+ matches = [p for p in available_parts if p.lower() == body_part.lower()]
724
+ if not matches:
725
+ selected_body_part = available_parts[0]
726
+ else:
727
+ selected_body_part = matches[0]
728
+
729
+ # Get column names
730
+ col_names = get_column_names(df, selected_body_part)
731
+ if not col_names or 'x' not in col_names or 'y' not in col_names:
732
+ return None, None, None, None
733
+
734
+ # Clean data
735
+ x, y, confidence = clean_data(df, col_names['x'], col_names['y'],
736
+ col_names.get('confidence'), outlier_threshold)
737
+
738
+ valid_count = (~x.isna() & ~y.isna() & (confidence >= confidence_threshold)).sum()
739
+ if valid_count < 10:
740
+ return None, None, None, None
741
+
742
+ # Calculate velocity and heatmap
743
+ try:
744
+ velocity = calculate_velocity(x, y, fps)
745
+ heatmap, xedges, yedges = calculate_velocity_heatmap(
746
+ x.values, y.values, velocity.values, confidence.values,
747
+ confidence_threshold, bins, sigma, arena_size
748
+ )
749
+ return heatmap, xedges, yedges, selected_body_part
750
+ except Exception as e:
751
+ return None, None, None, None
752
+
753
+
754
+ def main():
755
+ args = parse_args()
756
+ input_path = Path(args.input_path)
757
+
758
+ if not input_path.exists():
759
+ print(f"Error: Input path does not exist: {input_path}")
760
+ sys.exit(1)
761
+
762
+ arena_size = parse_arena_size(args.arena_size)
763
+ output_dir = get_output_dir(input_path, args.output_dir)
764
+ print(f"Output directory: {output_dir}")
765
+
766
+ if input_path.is_file():
767
+ if input_path.suffix.lower() not in ['.h5', '.hdf5', '.csv', '.txt']:
768
+ print(f"Error: Unsupported file format: {input_path.suffix}")
769
+ sys.exit(1)
770
+
771
+ result = process_single_file(
772
+ input_path, args.body_part, args.confidence_threshold,
773
+ args.fps, output_dir, args.cmap, args.bins, args.sigma,
774
+ arena_size, args.outlier_threshold, args.velocity_max, args.list_parts
775
+ )
776
+
777
+ if args.list_parts and result:
778
+ print(f"\nAvailable body parts: {', '.join(result)}")
779
+
780
+ elif input_path.is_dir():
781
+ files = sorted(input_path.glob("*.h5")) + sorted(input_path.glob("*.csv")) + sorted(input_path.glob("*.hdf5"))
782
+
783
+ if not files:
784
+ print(f"No .h5 or .csv files found in: {input_path}")
785
+ sys.exit(1)
786
+
787
+ print(f"Found {len(files)} files to process")
788
+ print("-" * 50)
789
+
790
+ # First pass: calculate all heatmaps and find global max for unified colorbar
791
+ print("\n[Pass 1/2] Calculating velocity heatmaps to determine unified colorbar range...")
792
+ all_heatmaps = []
793
+ global_max = 0.0
794
+
795
+ for i, file_path in enumerate(files, 1):
796
+ print(f" [{i}/{len(files)}] {file_path.name}")
797
+ heatmap, xedges, yedges, selected_body_part = calculate_file_heatmap(
798
+ file_path, args.body_part, args.confidence_threshold, args.fps,
799
+ args.bins, args.sigma, arena_size, args.outlier_threshold
800
+ )
801
+ if heatmap is not None:
802
+ all_heatmaps.append((file_path, heatmap, xedges, yedges, selected_body_part))
803
+ if np.any(heatmap > 0):
804
+ file_max = np.percentile(heatmap[heatmap > 0], 99)
805
+ global_max = max(global_max, file_max)
806
+
807
+ if not all_heatmaps:
808
+ print("No valid heatmaps could be calculated.")
809
+ sys.exit(1)
810
+
811
+ global_max = max(global_max, 0.1)
812
+ print(f"\nUnified colorbar range: 0 to {global_max:.4f}")
813
+ print("-" * 50)
814
+
815
+ # Second pass: generate visualizations with unified colorbar
816
+ print("\n[Pass 2/2] Generating visualizations with unified colorbar...")
817
+
818
+ for i, (file_path, heatmap, xedges, yedges, selected_body_part) in enumerate(all_heatmaps, 1):
819
+ print(f"\n[{i}/{len(all_heatmaps)}] {file_path.name}")
820
+
821
+ # Reload data for visualization
822
+ try:
823
+ df = load_tracking_data(file_path)
824
+ col_names = get_column_names(df, selected_body_part)
825
+ if not col_names:
826
+ continue
827
+ x, y, confidence = clean_data(df, col_names['x'], col_names['y'],
828
+ col_names.get('confidence'), args.outlier_threshold)
829
+
830
+ velocity = calculate_velocity(x, y, args.fps)
831
+
832
+ group = detect_group(file_path.stem)
833
+ title = f"{file_path.stem} ({group})"
834
+
835
+ fig = generate_velocity_heatmap_figure(
836
+ heatmap, xedges, yedges, x.values, y.values, velocity.values, confidence.values,
837
+ args.confidence_threshold, title, args.cmap, selected_body_part, global_max
838
+ )
839
+
840
+ output_dir.mkdir(parents=True, exist_ok=True)
841
+ output_file = output_dir / f"{file_path.stem}_velocity.png"
842
+ fig.savefig(output_file, dpi=150, bbox_inches='tight')
843
+ plt.close(fig)
844
+ print(f" Saved: {output_file}")
845
+
846
+ except Exception as e:
847
+ print(f" Error: {e}")
848
+ continue
849
+
850
+ print("\n" + "=" * 50)
851
+ print(f"All done! Results saved to: {output_dir}")
852
+
853
+
854
+ if __name__ == "__main__":
855
+ main()