@brainpilot/skills 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/01_Meta-Skills/academic-research-hub/SKILL.md +108 -0
- package/skills/01_Meta-Skills/academic-research-hub/scripts/requirements.txt +17 -0
- package/skills/01_Meta-Skills/academic-research-hub/scripts/research.py +781 -0
- package/skills/01_Meta-Skills/beautiful-log/SKILL.md +64 -0
- package/skills/01_Meta-Skills/beautiful-log/scripts/beautiful_log.py +274 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/SKILL.md +130 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/config.template.yaml +54 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/assets/top5_digest_template.md +5 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/build_top5_digest.py +300 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/common.py +137 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/merge_results.py +106 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/run_pipeline.py +177 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_arxiv.py +162 -0
- package/skills/01_Meta-Skills/ethoclaw-daily-paper/scripts/search_pubmed.py +202 -0
- package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/SKILL.md +173 -0
- package/skills/01_Meta-Skills/ethoclaw-normalize-tabular/scripts/normalize_data.py +874 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/SKILL.md +134 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/confirmation-prompts.md +31 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/references/output-patterns.md +45 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_markdown_deliverables.py +41 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_research_log.py +84 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/build_summary_md.py +63 -0
- package/skills/01_Meta-Skills/ethoclaw-pdf-research/scripts/extract_pdf_bundle.py +140 -0
- package/skills/01_Meta-Skills/experiment-controller/SKILL.md +140 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/SKILL.md +366 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/entity_resolution.py +120 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/extraction_prompt_template.txt +19 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/graph_query.py +106 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/hypothesis_cli_reference.py +42 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/new_data_source_template.py +116 -0
- package/skills/01_Meta-Skills/knowledge-graph-builder/scripts/requirements.txt +15 -0
- package/skills/01_Meta-Skills/method-design/SKILL.md +61 -0
- package/skills/01_Meta-Skills/multi-search-engine/SKILL.md +119 -0
- package/skills/01_Meta-Skills/research-idea/SKILL.md +65 -0
- package/skills/05_EEG_ERP/eeg-skill/SKILL.md +197 -0
- package/skills/05_EEG_ERP/meg-skill/SKILL.md +188 -0
- package/skills/05_EEG_ERP/meg-skill/scripts/time_frequency.py +223 -0
- package/skills/05_EEG_ERP/mne-eeg-tool/SKILL.md +165 -0
- package/skills/05_EEG_ERP/mne-eeg-tool/scripts/eeg_pipeline_reference.py +231 -0
- package/skills/05_EEG_ERP/seed-iv-skill/SKILL.md +184 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/classify_seed_iv.py +154 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/extract_seed_iv_features.py +190 -0
- package/skills/05_EEG_ERP/seed-iv-skill/scripts/validate_seed_iv.py +102 -0
- package/skills/05_EEG_ERP/seed-vig-skill/SKILL.md +182 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/classify_seed_vig.py +165 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/extract_seed_vig_features.py +185 -0
- package/skills/05_EEG_ERP/seed-vig-skill/scripts/validate_seed_vig.py +88 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/SKILL.md +308 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/abcd_qc_summary.py +449 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/extract_abcd_phenotype.py +292 -0
- package/skills/06_fMRI_Neuroimaging/abcd-skill/scripts/reorganize_abcd.py +387 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/SKILL.md +302 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/abide_qc_summary.py +317 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/extract_abide_phenotype.py +267 -0
- package/skills/06_fMRI_Neuroimaging/abide-skill/scripts/reorganize_abide.py +387 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/SKILL.md +244 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/adhd200_qc_summary.py +98 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/extract_adhd200_phenotype.py +134 -0
- package/skills/06_fMRI_Neuroimaging/adhd200-skill/scripts/reorganize_adhd200.py +206 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/SKILL.md +358 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_adni_task_files.py +1305 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/generate_vqa_from_tasks.py +766 -0
- package/skills/06_fMRI_Neuroimaging/adni-skill/scripts/reorganize_adni.py +491 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/SKILL.md +295 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/aibl_qc_summary.py +260 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/extract_aibl_phenotype.py +365 -0
- package/skills/06_fMRI_Neuroimaging/aibl-skill/scripts/reorganize_aibl.py +394 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/SKILL.md +292 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/aomic_qc_summary.py +258 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/extract_aomic_phenotype.py +284 -0
- package/skills/06_fMRI_Neuroimaging/aomic-skill/scripts/reorganize_aomic.py +322 -0
- package/skills/06_fMRI_Neuroimaging/asl-skill/SKILL.md +168 -0
- package/skills/06_fMRI_Neuroimaging/asl-skill/scripts/compute_cbf.py +224 -0
- package/skills/06_fMRI_Neuroimaging/bids-organizer/SKILL.md +241 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/SKILL.md +186 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/bold5000_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/extract_bold5000_stimulus.py +125 -0
- package/skills/06_fMRI_Neuroimaging/bold5000-skill/scripts/reorganize_bold5000.py +102 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/SKILL.md +213 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/camcan_qc_summary.py +131 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/extract_camcan_phenotype.py +145 -0
- package/skills/06_fMRI_Neuroimaging/camcan-skill/scripts/validate_camcan.py +141 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/SKILL.md +201 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/cobre_qc_summary.py +95 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/extract_cobre_phenotype.py +104 -0
- package/skills/06_fMRI_Neuroimaging/cobre-skill/scripts/reorganize_cobre.py +140 -0
- package/skills/06_fMRI_Neuroimaging/conn-tool/SKILL.md +180 -0
- package/skills/06_fMRI_Neuroimaging/dcm2nii/SKILL.md +189 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/SKILL.md +183 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/dmt_har_med_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/extract_dmt_har_med_phenotype.py +121 -0
- package/skills/06_fMRI_Neuroimaging/dmt-har-med-skill/scripts/reorganize_dmt_har_med.py +125 -0
- package/skills/06_fMRI_Neuroimaging/dwi-skill/SKILL.md +359 -0
- package/skills/06_fMRI_Neuroimaging/fmri-skill/SKILL.md +371 -0
- package/skills/06_fMRI_Neuroimaging/fmriprep-tool/SKILL.md +228 -0
- package/skills/06_fMRI_Neuroimaging/freesurfer-tool/SKILL.md +286 -0
- package/skills/06_fMRI_Neuroimaging/freesurfer-tool/scripts/freesurfer_processor.py +145 -0
- package/skills/06_fMRI_Neuroimaging/fsl-tool/SKILL.md +208 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/SKILL.md +271 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/extract_hbn_phenotype.py +107 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/hbn_qc_summary.py +96 -0
- package/skills/06_fMRI_Neuroimaging/hbn-skill/scripts/reorganize_hbn.py +150 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/extract_hcpa_phenotype.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/hcpa_qc_summary.py +120 -0
- package/skills/06_fMRI_Neuroimaging/hcpa-skill/scripts/reorganize_hcpa.py +155 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/extract_hcpd_phenotype.py +148 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/hcpd_qc_summary.py +125 -0
- package/skills/06_fMRI_Neuroimaging/hcpd-skill/scripts/reorganize_hcpd.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/SKILL.md +215 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/extract_hcpep_phenotype.py +157 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/hcpep_qc_summary.py +143 -0
- package/skills/06_fMRI_Neuroimaging/hcpep-skill/scripts/reorganize_hcpep.py +146 -0
- package/skills/06_fMRI_Neuroimaging/hcppipeline-tool/SKILL.md +217 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/SKILL.md +214 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/extract_hcpya_phenotype.py +190 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/hcpya_qc_summary.py +152 -0
- package/skills/06_fMRI_Neuroimaging/hcpya-skill/scripts/reorganize_hcpya.py +203 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/SKILL.md +198 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/ixi_qc_summary.py +137 -0
- package/skills/06_fMRI_Neuroimaging/ixi-skill/scripts/reorganize_ixi.py +190 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/SKILL.md +191 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/extract_mnd_phenotype.py +143 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/mnd_qc_summary.py +120 -0
- package/skills/06_fMRI_Neuroimaging/mnd-skill/scripts/validate_mnd.py +107 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/SKILL.md +203 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/analyze_lesions.py +119 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/longitudinal_lesion.py +148 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/mschallenge_qc_summary.py +132 -0
- package/skills/06_fMRI_Neuroimaging/mschallenge-skill/scripts/validate_mschallenge.py +116 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/SKILL.md +184 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/atlas_coordinate_reference.py +61 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/freesurfer_io_reference.py +34 -0
- package/skills/06_fMRI_Neuroimaging/nibabel-skill/scripts/nifti_inspection_reference.py +35 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/SKILL.md +205 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/extract_nifd_phenotype.py +132 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/nifd_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/nifd-skill/scripts/validate_nifd.py +111 -0
- package/skills/06_fMRI_Neuroimaging/nii2dcm/SKILL.md +143 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/SKILL.md +266 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/connectome_reference.py +65 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/denoise_timeseries_reference.py +58 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/hierarchical_parcellation_reference.py +53 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/kmeans_parcellation_reference.py +53 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/preprocess_bold_reference.py +76 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_dictlearning_reference.py +56 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/rest_ica_reference.py +59 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/second_level_glm_reference.py +58 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/spacenet_classifier_reference.py +59 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/svm_classifier_reference.py +60 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/task_glm_reference.py +63 -0
- package/skills/06_fMRI_Neuroimaging/nilearn-tool/scripts/zalff_summary_reference.py +109 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/SKILL.md +210 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/extract_nsd_stimulus.py +171 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/nsd_qc_summary.py +142 -0
- package/skills/06_fMRI_Neuroimaging/nsd-skill/scripts/validate_nsd.py +142 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/SKILL.md +205 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/extract_oasis_phenotype.py +126 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/oasis_qc_summary.py +115 -0
- package/skills/06_fMRI_Neuroimaging/oasis-skill/scripts/validate_oasis.py +119 -0
- package/skills/06_fMRI_Neuroimaging/pet-skill/SKILL.md +173 -0
- package/skills/06_fMRI_Neuroimaging/pet-skill/scripts/compute_suvr.py +202 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/SKILL.md +206 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/extract_pnc_phenotype.py +136 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/pnc_qc_summary.py +116 -0
- package/skills/06_fMRI_Neuroimaging/pnc-skill/scripts/validate_pnc.py +120 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/SKILL.md +209 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/extract_ppmi_phenotype.py +138 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/ppmi_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/ppmi-skill/scripts/validate_ppmi.py +117 -0
- package/skills/06_fMRI_Neuroimaging/qsiprep-tool/SKILL.md +320 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/SKILL.md +215 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/extract_rest_mdd_phenotype.py +132 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/harmonize_sites.py +152 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/rest_mdd_qc_summary.py +124 -0
- package/skills/06_fMRI_Neuroimaging/rest-mneta-mdd-skill/scripts/validate_rest_mdd.py +103 -0
- package/skills/06_fMRI_Neuroimaging/smri-skill/SKILL.md +302 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/SKILL.md +204 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/extract_tcp_phenotype.py +139 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/tcp_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/tcp-skill/scripts/validate_tcp.py +99 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/SKILL.md +217 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/extract_ucla_cnp_phenotype.py +145 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/ucla_cnp_qc_summary.py +111 -0
- package/skills/06_fMRI_Neuroimaging/ucla-cnp-skill/scripts/validate_ucla_cnp.py +113 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/SKILL.md +310 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/build_ukb_survival.py +210 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_cases.py +308 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/extract_ukb_phenotype.py +232 -0
- package/skills/06_fMRI_Neuroimaging/ukb-skill/scripts/ukb_qc_summary.py +158 -0
- package/skills/06_fMRI_Neuroimaging/wmh-segmentation/SKILL.md +133 -0
- package/skills/07_Computational_Modeling/detrending/SKILL.md +118 -0
- package/skills/07_Computational_Modeling/dictlearning/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/filtering/SKILL.md +121 -0
- package/skills/07_Computational_Modeling/glm/SKILL.md +153 -0
- package/skills/07_Computational_Modeling/hierarchical/SKILL.md +121 -0
- package/skills/07_Computational_Modeling/ica/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/kmeans/SKILL.md +119 -0
- package/skills/07_Computational_Modeling/run_models/SKILL.md +427 -0
- package/skills/07_Computational_Modeling/spacenet/SKILL.md +122 -0
- package/skills/07_Computational_Modeling/svm/SKILL.md +120 -0
- package/skills/08_Computational_Neuroscience/brain_gnn/SKILL.md +183 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/SKILL.md +239 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/dti_metrics_reference.py +70 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/load_and_mask_reference.py +76 -0
- package/skills/08_Computational_Neuroscience/dipy-tool/scripts/roi_stats_reference.py +59 -0
- package/skills/08_Computational_Neuroscience/fm_app/SKILL.md +195 -0
- package/skills/08_Computational_Neuroscience/neurostorm/SKILL.md +151 -0
- package/skills/13_Visualization/brain-visualization/SKILL.md +191 -0
- package/skills/13_Visualization/brain-visualization/scripts/connectome_reference.py +108 -0
- package/skills/13_Visualization/brain-visualization/scripts/freesurfer_ply_reference.py +54 -0
- package/skills/13_Visualization/brain-visualization/scripts/zalff_summary_reference.py +116 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/SKILL.md +78 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/assets/naturecomm_figures.tex +74 -0
- package/skills/13_Visualization/ethoclaw-paper-figure-layout/scripts/layout_results_foldered.py +579 -0
- package/skills/14_Writing/overleaf-skill/SKILL.md +184 -0
- package/skills/14_Writing/overleaf-skill/scripts/install.sh +30 -0
- package/skills/14_Writing/paper-writing/SKILL.md +146 -0
- package/skills/14_Writing/paper-writing/scripts/data_statement_templates.py +164 -0
- package/skills/14_Writing/paper-writing/scripts/figure_templates.py +315 -0
- package/skills/14_Writing/paper-writing/scripts/nature_figure_style.py +214 -0
- package/skills/14_Writing/paper-writing/scripts/section_phrasebank.py +246 -0
- package/skills/16_Animal_Behavior/deeplabcut/SKILL.md +154 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/3d-pose.md +89 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/maDLC.md +123 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/modelzoo.md +98 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/standard-pipeline.md +165 -0
- package/skills/16_Animal_Behavior/deeplabcut/references/utilities.md +146 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/SKILL.md +274 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.html +112 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/report_template_en.md +21 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/cluster-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/heatmap-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/integrated-interpretation.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/overview.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/project-summary.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/radar-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/raw-trajectory.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/sample-check.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/single-subject-section.md +3 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/assets/section_templates/stats-section.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/epm.md +52 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/fst.md +37 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/nor.md +39 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/oft.md +43 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tcst.md +45 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/experiment-types/tst.md +36 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/input-types.md +59 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/interpretation-guardrails.md +45 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/metadata-schema.md +57 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/report-sections.md +86 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/references/section-selection-rules.md +169 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/build_report_manifest.py +27 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/render_report.py +34 -0
- package/skills/16_Animal_Behavior/ethoclaw-analysis-report/scripts/report_utils.py +1121 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/SKILL.md +390 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-grounding/reference_code.py +98 -0
- package/skills/16_Animal_Behavior/ethoclaw-animal-pose-estimation/SKILL.md +336 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/README.md +21 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/SKILL.md +41 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/batch_kinematic_generator.py +663 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/config.json +19 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/generate_kinematic_parameter.py +401 -0
- package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/kinematic_generator.py +265 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/SKILL.md +72 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/references/config.example.toml +56 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params.py +232 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-clustermap-generate/scripts/cluster_all_params_from_config.py +236 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/SKILL.md +68 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/references/notes.md +5 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-radar-generate/scripts/plot_h5_radar.py +513 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/SKILL.md +52 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/config.toml +81 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/references/stats-rule.md +18 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_inspect.py +79 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_batch.py +624 -0
- package/skills/16_Animal_Behavior/ethoclaw-multiparameter-violin-stats-generate/scripts/h5_violin_stats.py +438 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/SKILL.md +280 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_trajectory.py +790 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/core_scripts/heatmap_velocity.py +855 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.csv +101 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_2d.h5 +0 -0
- package/skills/16_Animal_Behavior/ethoclaw-trajectory-velocity-heatmap-generate/reference_data/reference_data_readme.md +126 -0
|
@@ -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()
|