@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,401 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Generate /KinematicParameter in an existing .h5 file.
|
|
5
|
+
|
|
6
|
+
Reads:
|
|
7
|
+
/2Dskeleton/BodyParts (n_bodyparts,)
|
|
8
|
+
/2Dskeleton/data2D (n_frames, n_bodyparts*3) -> [x, y, likelihood] per bodypart
|
|
9
|
+
|
|
10
|
+
Uses FPS from:
|
|
11
|
+
1) /VideoInfo/fps if present and >0
|
|
12
|
+
2) if missing/0 and --search-video is enabled: try to locate a same-stem video
|
|
13
|
+
(e.g. rec-1-xxx.mp4/.avi/...) under common folders and read FPS from it.
|
|
14
|
+
3) fallback to --fps-default
|
|
15
|
+
|
|
16
|
+
Optionally uses calibration from:
|
|
17
|
+
/CalibrationInfo/px_mm_ratio_x, /CalibrationInfo/px_mm_ratio_y
|
|
18
|
+
|
|
19
|
+
Writes (in-place):
|
|
20
|
+
/KinematicParameter/ParameterName (n_params,)
|
|
21
|
+
/KinematicParameter/ParameterData (n_frames, n_params)
|
|
22
|
+
|
|
23
|
+
n_params = 4*n_bodyparts + 2
|
|
24
|
+
(x_, y_, distance_, speed_) per bodypart + (frame_distance, frame_speed)
|
|
25
|
+
|
|
26
|
+
NOTE: This script edits the input file in-place.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import os
|
|
31
|
+
import shutil
|
|
32
|
+
import subprocess
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Iterable, List, Optional
|
|
35
|
+
|
|
36
|
+
import numpy as np
|
|
37
|
+
import h5py
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
VIDEO_EXTENSIONS = [
|
|
41
|
+
".mp4",
|
|
42
|
+
".avi",
|
|
43
|
+
".mkv",
|
|
44
|
+
".mov",
|
|
45
|
+
".wmv",
|
|
46
|
+
".flv",
|
|
47
|
+
".mpeg",
|
|
48
|
+
".mpg",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _decode(x):
|
|
53
|
+
if isinstance(x, (bytes, np.bytes_)):
|
|
54
|
+
return x.decode("utf-8", errors="ignore")
|
|
55
|
+
return str(x)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _as_bytes_array(str_list: List[str]):
|
|
59
|
+
# store as fixed-width bytes in HDF5 for portability
|
|
60
|
+
return np.array([s.encode("utf-8") for s in str_list], dtype="S")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def fps_from_video_opencv(video_path: Path) -> float:
|
|
64
|
+
try:
|
|
65
|
+
import cv2 # type: ignore
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
"OpenCV (cv2) is not available. Install it in your conda env, e.g.\n"
|
|
69
|
+
" conda install -c conda-forge opencv\n"
|
|
70
|
+
f"Original import error: {e}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
cap = cv2.VideoCapture(str(video_path))
|
|
74
|
+
fps = float(cap.get(cv2.CAP_PROP_FPS))
|
|
75
|
+
cap.release()
|
|
76
|
+
if not fps or fps <= 1e-6:
|
|
77
|
+
raise RuntimeError(f"Cannot read FPS via OpenCV from: {video_path}")
|
|
78
|
+
return fps
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _parse_rate(rate: str) -> Optional[float]:
|
|
82
|
+
rate = (rate or "").strip()
|
|
83
|
+
if not rate:
|
|
84
|
+
return None
|
|
85
|
+
if "/" in rate:
|
|
86
|
+
a, b = rate.split("/", 1)
|
|
87
|
+
try:
|
|
88
|
+
a = float(a)
|
|
89
|
+
b = float(b)
|
|
90
|
+
if b != 0:
|
|
91
|
+
return a / b
|
|
92
|
+
except Exception:
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
return float(rate)
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def fps_from_video_ffprobe(video_path: Path) -> float:
|
|
101
|
+
ffprobe = shutil.which("ffprobe")
|
|
102
|
+
if not ffprobe:
|
|
103
|
+
raise RuntimeError(
|
|
104
|
+
"ffprobe is not available on PATH. Install ffmpeg/ffprobe or use OpenCV.\n"
|
|
105
|
+
"- On conda (recommended): conda install -c conda-forge ffmpeg\n"
|
|
106
|
+
"- Or: conda install -c conda-forge opencv"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Try avg_frame_rate first, then r_frame_rate
|
|
110
|
+
cmd = [
|
|
111
|
+
ffprobe,
|
|
112
|
+
"-v",
|
|
113
|
+
"error",
|
|
114
|
+
"-select_streams",
|
|
115
|
+
"v:0",
|
|
116
|
+
"-show_entries",
|
|
117
|
+
"stream=avg_frame_rate,r_frame_rate",
|
|
118
|
+
"-of",
|
|
119
|
+
"default=nk=1:nw=1",
|
|
120
|
+
str(video_path),
|
|
121
|
+
]
|
|
122
|
+
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True).strip().splitlines()
|
|
123
|
+
# output lines: avg_frame_rate, r_frame_rate (order not strictly guaranteed)
|
|
124
|
+
rates = [_parse_rate(x) for x in out]
|
|
125
|
+
rates = [x for x in rates if x and x > 1e-6]
|
|
126
|
+
if not rates:
|
|
127
|
+
raise RuntimeError(f"Cannot parse FPS from ffprobe output for: {video_path}\nOutput: {out}")
|
|
128
|
+
# prefer a sensible fps
|
|
129
|
+
return float(rates[0])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def fps_from_video(video_path: Path) -> float:
|
|
133
|
+
"""Try OpenCV first (if installed), otherwise ffprobe."""
|
|
134
|
+
try:
|
|
135
|
+
return fps_from_video_opencv(video_path)
|
|
136
|
+
except Exception:
|
|
137
|
+
# fall back to ffprobe
|
|
138
|
+
return fps_from_video_ffprobe(video_path)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _candidate_search_dirs(h5_path: Path, max_parent_levels: int = 2) -> List[Path]:
|
|
142
|
+
"""Generate likely directories for locating a same-stem video."""
|
|
143
|
+
h5_dir = h5_path.parent
|
|
144
|
+
subs = ["videos", "video", "Videos", "Video", "data", "Data"]
|
|
145
|
+
|
|
146
|
+
dirs: List[Path] = []
|
|
147
|
+
# current dir and common subdirs
|
|
148
|
+
dirs.append(h5_dir)
|
|
149
|
+
for s in subs:
|
|
150
|
+
dirs.append(h5_dir / s)
|
|
151
|
+
|
|
152
|
+
# parent dirs and their common subdirs
|
|
153
|
+
cur = h5_dir
|
|
154
|
+
for _ in range(max_parent_levels):
|
|
155
|
+
cur = cur.parent
|
|
156
|
+
dirs.append(cur)
|
|
157
|
+
for s in subs:
|
|
158
|
+
dirs.append(cur / s)
|
|
159
|
+
|
|
160
|
+
# de-dup while preserving order
|
|
161
|
+
seen = set()
|
|
162
|
+
out = []
|
|
163
|
+
for d in dirs:
|
|
164
|
+
dp = d.resolve() if d.exists() else d
|
|
165
|
+
key = str(dp)
|
|
166
|
+
if key not in seen:
|
|
167
|
+
seen.add(key)
|
|
168
|
+
out.append(d)
|
|
169
|
+
return out
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def find_video_for_h5(
|
|
173
|
+
h5_path: Path,
|
|
174
|
+
exts: Iterable[str] = VIDEO_EXTENSIONS,
|
|
175
|
+
max_parent_levels: int = 2,
|
|
176
|
+
) -> Optional[Path]:
|
|
177
|
+
"""Search for a same-stem video file near the h5 path."""
|
|
178
|
+
stem = h5_path.stem
|
|
179
|
+
exts = [e if e.startswith(".") else "." + e for e in exts]
|
|
180
|
+
|
|
181
|
+
for d in _candidate_search_dirs(h5_path, max_parent_levels=max_parent_levels):
|
|
182
|
+
if not d.exists() or not d.is_dir():
|
|
183
|
+
continue
|
|
184
|
+
# direct same-stem match
|
|
185
|
+
for ext in exts:
|
|
186
|
+
p = d / (stem + ext)
|
|
187
|
+
if p.exists():
|
|
188
|
+
return p
|
|
189
|
+
# case-insensitive check (Windows naming via WSL can be odd)
|
|
190
|
+
try:
|
|
191
|
+
for cand in d.glob(stem + ".*"):
|
|
192
|
+
if cand.suffix.lower() == ext.lower() and cand.exists():
|
|
193
|
+
return cand
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def load_fps(
|
|
201
|
+
h5,
|
|
202
|
+
h5_path: Path,
|
|
203
|
+
fps_default: float = 30.0,
|
|
204
|
+
search_video: bool = True,
|
|
205
|
+
video_path: Optional[Path] = None,
|
|
206
|
+
max_parent_levels: int = 2,
|
|
207
|
+
) -> float:
|
|
208
|
+
"""Load fps from h5, or from a located video, or fallback."""
|
|
209
|
+
fps = None
|
|
210
|
+
if "VideoInfo" in h5 and "fps" in h5["VideoInfo"]:
|
|
211
|
+
try:
|
|
212
|
+
fps = float(h5["VideoInfo"]["fps"][()])
|
|
213
|
+
except Exception:
|
|
214
|
+
fps = None
|
|
215
|
+
|
|
216
|
+
if fps is not None and fps > 1e-6:
|
|
217
|
+
return float(fps)
|
|
218
|
+
|
|
219
|
+
if search_video:
|
|
220
|
+
vp = Path(video_path) if video_path else find_video_for_h5(h5_path, max_parent_levels=max_parent_levels)
|
|
221
|
+
if vp is not None:
|
|
222
|
+
return float(fps_from_video(vp))
|
|
223
|
+
|
|
224
|
+
# if search_video enabled but not found, ask user by failing loudly
|
|
225
|
+
raise RuntimeError(
|
|
226
|
+
"FPS not found in H5 (/VideoInfo/fps missing or 0), and no same-stem video file was found.\n"
|
|
227
|
+
"Searched: current folder, ./videos, ./data, and parent-level videos/data folders.\n"
|
|
228
|
+
"Please provide the exact video path via --video, or disable video search and use --fps-default."
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return float(fps_default)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def load_calibration(h5, ratio_default: float = 1.0):
|
|
235
|
+
rx, ry = float(ratio_default), float(ratio_default)
|
|
236
|
+
if "CalibrationInfo" in h5:
|
|
237
|
+
ci = h5["CalibrationInfo"]
|
|
238
|
+
if "px_mm_ratio_x" in ci:
|
|
239
|
+
try:
|
|
240
|
+
rx = float(ci["px_mm_ratio_x"][()])
|
|
241
|
+
except Exception:
|
|
242
|
+
pass
|
|
243
|
+
if "px_mm_ratio_y" in ci:
|
|
244
|
+
try:
|
|
245
|
+
ry = float(ci["px_mm_ratio_y"][()])
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
if rx == 0:
|
|
249
|
+
rx = float(ratio_default)
|
|
250
|
+
if ry == 0:
|
|
251
|
+
ry = float(ratio_default)
|
|
252
|
+
return rx, ry
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def compute_kinematic(bodyparts: List[str], data2d: np.ndarray, fps: float, rx: float, ry: float):
|
|
256
|
+
"""Compute per-frame kinematic features."""
|
|
257
|
+
n_frames = int(data2d.shape[0])
|
|
258
|
+
n_bp = int(len(bodyparts))
|
|
259
|
+
|
|
260
|
+
if data2d.ndim != 2:
|
|
261
|
+
raise ValueError(f"data2D must be 2D. Got shape={data2d.shape}")
|
|
262
|
+
|
|
263
|
+
if data2d.shape[1] < n_bp * 3:
|
|
264
|
+
raise ValueError(f"data2D second dim too small: got {data2d.shape}, need at least {n_bp*3}")
|
|
265
|
+
|
|
266
|
+
cols = []
|
|
267
|
+
names: List[str] = []
|
|
268
|
+
|
|
269
|
+
# per-bodypart x,y,distance,speed
|
|
270
|
+
for i, bp in enumerate(bodyparts):
|
|
271
|
+
x = data2d[:, i * 3 + 0].astype(np.float64)
|
|
272
|
+
y = data2d[:, i * 3 + 1].astype(np.float64)
|
|
273
|
+
|
|
274
|
+
# px -> mm (divide by px/mm) -> cm (/10)
|
|
275
|
+
x_cm = x / rx / 10.0
|
|
276
|
+
y_cm = y / ry / 10.0
|
|
277
|
+
|
|
278
|
+
pts = np.stack([x_cm, y_cm], axis=1)
|
|
279
|
+
d = np.linalg.norm(np.diff(pts, axis=0), axis=1) # frames-1
|
|
280
|
+
d = np.pad(d, (0, 1), mode="constant", constant_values=0.0) # to frames
|
|
281
|
+
speed = d * fps
|
|
282
|
+
|
|
283
|
+
names += [f"x_{bp}", f"y_{bp}", f"distance_{bp}", f"speed_{bp}"]
|
|
284
|
+
cols += [x_cm, y_cm, d, speed]
|
|
285
|
+
|
|
286
|
+
# frame_distance / frame_speed from mean point of all bodyparts
|
|
287
|
+
xs = []
|
|
288
|
+
ys = []
|
|
289
|
+
for i in range(n_bp):
|
|
290
|
+
xs.append(data2d[:, i * 3 + 0].astype(np.float64) / rx / 10.0)
|
|
291
|
+
ys.append(data2d[:, i * 3 + 1].astype(np.float64) / ry / 10.0)
|
|
292
|
+
mean_x = np.mean(np.stack(xs, axis=1), axis=1)
|
|
293
|
+
mean_y = np.mean(np.stack(ys, axis=1), axis=1)
|
|
294
|
+
mean_pts = np.stack([mean_x, mean_y], axis=1)
|
|
295
|
+
|
|
296
|
+
frame_d = np.linalg.norm(np.diff(mean_pts, axis=0), axis=1)
|
|
297
|
+
frame_d = np.insert(frame_d, 0, frame_d[0] if frame_d.size else 0.0)
|
|
298
|
+
if frame_d.shape[0] < n_frames:
|
|
299
|
+
frame_d = np.pad(frame_d, (0, n_frames - frame_d.shape[0]), mode="constant", constant_values=0.0)
|
|
300
|
+
frame_speed = frame_d * fps
|
|
301
|
+
|
|
302
|
+
names += ["frame_distance", "frame_speed"]
|
|
303
|
+
cols += [frame_d, frame_speed]
|
|
304
|
+
|
|
305
|
+
param_data = np.stack(cols, axis=1).astype(np.float32)
|
|
306
|
+
return names, param_data
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def write_kinematic(
|
|
310
|
+
h5_path: Path,
|
|
311
|
+
overwrite: bool,
|
|
312
|
+
fps_default: float,
|
|
313
|
+
ratio_default: float,
|
|
314
|
+
search_video: bool,
|
|
315
|
+
video_path: Optional[Path],
|
|
316
|
+
max_parent_levels: int,
|
|
317
|
+
):
|
|
318
|
+
h5_path = Path(h5_path)
|
|
319
|
+
if not h5_path.exists():
|
|
320
|
+
raise FileNotFoundError(h5_path)
|
|
321
|
+
|
|
322
|
+
with h5py.File(str(h5_path), "r+") as h5:
|
|
323
|
+
if "2Dskeleton" not in h5:
|
|
324
|
+
raise KeyError("Missing group: /2Dskeleton")
|
|
325
|
+
sk = h5["2Dskeleton"]
|
|
326
|
+
if "BodyParts" not in sk or "data2D" not in sk:
|
|
327
|
+
raise KeyError("Missing datasets: /2Dskeleton/BodyParts or /2Dskeleton/data2D")
|
|
328
|
+
|
|
329
|
+
bodyparts = [_decode(x) for x in np.array(sk["BodyParts"][()])]
|
|
330
|
+
data2d = np.array(sk["data2D"][()])
|
|
331
|
+
|
|
332
|
+
fps = load_fps(
|
|
333
|
+
h5,
|
|
334
|
+
h5_path=h5_path,
|
|
335
|
+
fps_default=fps_default,
|
|
336
|
+
search_video=search_video,
|
|
337
|
+
video_path=video_path,
|
|
338
|
+
max_parent_levels=max_parent_levels,
|
|
339
|
+
)
|
|
340
|
+
rx, ry = load_calibration(h5, ratio_default=ratio_default)
|
|
341
|
+
|
|
342
|
+
names, param_data = compute_kinematic(bodyparts, data2d, fps, rx, ry)
|
|
343
|
+
|
|
344
|
+
if "KinematicParameter" in h5:
|
|
345
|
+
if not overwrite:
|
|
346
|
+
raise SystemExit("KinematicParameter already exists. Re-run with --overwrite to replace it.")
|
|
347
|
+
del h5["KinematicParameter"]
|
|
348
|
+
|
|
349
|
+
grp = h5.create_group("KinematicParameter")
|
|
350
|
+
grp.create_dataset("ParameterName", data=_as_bytes_array(names))
|
|
351
|
+
grp.create_dataset("ParameterData", data=param_data, compression="gzip", compression_opts=4)
|
|
352
|
+
|
|
353
|
+
grp.attrs["generated_by"] = "generate_kinematic_parameter.py"
|
|
354
|
+
grp.attrs["fps_used"] = float(fps)
|
|
355
|
+
grp.attrs["px_mm_ratio_x_used"] = float(rx)
|
|
356
|
+
grp.attrs["px_mm_ratio_y_used"] = float(ry)
|
|
357
|
+
|
|
358
|
+
print(f"[OK] Wrote /KinematicParameter to: {h5_path}")
|
|
359
|
+
print(f" frames={param_data.shape[0]} params={param_data.shape[1]}")
|
|
360
|
+
print(f" fps={fps} rx={rx} ry={ry}")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def main():
|
|
364
|
+
ap = argparse.ArgumentParser()
|
|
365
|
+
ap.add_argument("--h5", required=True, help="Path to .h5 file")
|
|
366
|
+
ap.add_argument("--overwrite", action="store_true", help="Overwrite existing /KinematicParameter")
|
|
367
|
+
|
|
368
|
+
ap.add_argument("--fps-default", type=float, default=30.0, help="Fallback fps when no fps and video search disabled")
|
|
369
|
+
ap.add_argument("--ratio-default", type=float, default=1.0, help="Fallback px/mm ratio when missing")
|
|
370
|
+
|
|
371
|
+
ap.add_argument(
|
|
372
|
+
"--search-video",
|
|
373
|
+
action=argparse.BooleanOptionalAction,
|
|
374
|
+
default=True,
|
|
375
|
+
help="When H5 has no fps, try to locate same-stem video and read FPS (default: true)",
|
|
376
|
+
)
|
|
377
|
+
ap.add_argument("--video", default="", help="Explicit video path (overrides auto search when provided)")
|
|
378
|
+
ap.add_argument(
|
|
379
|
+
"--max-parent-levels",
|
|
380
|
+
type=int,
|
|
381
|
+
default=2,
|
|
382
|
+
help="How many parent levels to search for videos/data folders (default: 2)",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
args = ap.parse_args()
|
|
386
|
+
|
|
387
|
+
video_path = Path(args.video) if str(args.video).strip() else None
|
|
388
|
+
|
|
389
|
+
write_kinematic(
|
|
390
|
+
h5_path=Path(args.h5),
|
|
391
|
+
overwrite=bool(args.overwrite),
|
|
392
|
+
fps_default=float(args.fps_default),
|
|
393
|
+
ratio_default=float(args.ratio_default),
|
|
394
|
+
search_video=bool(args.search_video),
|
|
395
|
+
video_path=video_path,
|
|
396
|
+
max_parent_levels=int(args.max_parent_levels),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
main()
|
package/skills/16_Animal_Behavior/ethoclaw-kinematic-parameter-generator/kinematic_generator.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Kinematic Parameter Generator Skill
|
|
5
|
+
|
|
6
|
+
This skill checks if a .h5 file contains KinematicParameter data, and if not,
|
|
7
|
+
generates it from the 2Dskeleton data using the generate_kinematic_parameter.py logic.
|
|
8
|
+
|
|
9
|
+
If FPS is not available in the .h5 file, it attempts to read it from the associated video file
|
|
10
|
+
using either ffmpeg or OpenCV.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import h5py
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_kinematic_parameter_exists(h5_path):
|
|
22
|
+
"""
|
|
23
|
+
Check if KinematicParameter exists in the .h5 file
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
h5_path (str or Path): Path to the .h5 file
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
bool: True if KinematicParameter exists, False otherwise
|
|
30
|
+
"""
|
|
31
|
+
h5_path = Path(h5_path)
|
|
32
|
+
|
|
33
|
+
with h5py.File(str(h5_path), 'r') as h5_file:
|
|
34
|
+
return 'KinematicParameter' in h5_file
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_skeleton_data_exists(h5_path):
|
|
38
|
+
"""
|
|
39
|
+
Check if 2Dskeleton data exists in the .h5 file
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
h5_path (str or Path): Path to the .h5 file
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
bool: True if 2Dskeleton data exists, False otherwise
|
|
46
|
+
"""
|
|
47
|
+
h5_path = Path(h5_path)
|
|
48
|
+
|
|
49
|
+
with h5py.File(str(h5_path), 'r') as h5_file:
|
|
50
|
+
return ('2Dskeleton' in h5_file and
|
|
51
|
+
'BodyParts' in h5_file['2Dskeleton'] and
|
|
52
|
+
'data2D' in h5_file['2Dskeleton'])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_fps_from_h5(h5_path):
|
|
56
|
+
"""
|
|
57
|
+
Get FPS from the .h5 file if available
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
h5_path (str or Path): Path to the .h5 file
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
float or None: FPS value if found and valid, None otherwise
|
|
64
|
+
"""
|
|
65
|
+
h5_path = Path(h5_path)
|
|
66
|
+
|
|
67
|
+
with h5py.File(str(h5_path), 'r') as h5_file:
|
|
68
|
+
if 'VideoInfo' in h5_file and 'fps' in h5_file['VideoInfo']:
|
|
69
|
+
try:
|
|
70
|
+
fps = float(h5_file['VideoInfo']['fps'][()])
|
|
71
|
+
if fps > 0:
|
|
72
|
+
return fps
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def find_associated_video(h5_path):
|
|
79
|
+
"""
|
|
80
|
+
Find an associated video file with the same stem as the .h5 file
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
h5_path (str or Path): Path to the .h5 file
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Path or None: Path to the video file if found, None otherwise
|
|
87
|
+
"""
|
|
88
|
+
h5_path = Path(h5_path)
|
|
89
|
+
stem = h5_path.stem
|
|
90
|
+
|
|
91
|
+
# Common video extensions
|
|
92
|
+
video_extensions = ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.mpeg', '.mpg']
|
|
93
|
+
|
|
94
|
+
# Look in the same directory as the .h5 file
|
|
95
|
+
h5_dir = h5_path.parent
|
|
96
|
+
|
|
97
|
+
for ext in video_extensions:
|
|
98
|
+
video_path = h5_dir / (stem + ext)
|
|
99
|
+
if video_path.exists():
|
|
100
|
+
return video_path
|
|
101
|
+
|
|
102
|
+
# If not found in the same directory, look in common subdirectories
|
|
103
|
+
for subdir in ['videos', 'video', 'Videos', 'Video', 'data', 'Data']:
|
|
104
|
+
sub_dir = h5_dir / subdir
|
|
105
|
+
if sub_dir.exists() and sub_dir.is_dir():
|
|
106
|
+
for ext in video_extensions:
|
|
107
|
+
video_path = sub_dir / (stem + ext)
|
|
108
|
+
if video_path.exists():
|
|
109
|
+
return video_path
|
|
110
|
+
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_fps_from_video(video_path):
|
|
115
|
+
"""
|
|
116
|
+
Get FPS from a video file using ffmpeg
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
video_path (str or Path): Path to the video file
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
float: FPS value
|
|
123
|
+
"""
|
|
124
|
+
import subprocess
|
|
125
|
+
import shutil
|
|
126
|
+
|
|
127
|
+
video_path = Path(video_path)
|
|
128
|
+
|
|
129
|
+
# Check if ffprobe is available
|
|
130
|
+
if shutil.which('ffprobe'):
|
|
131
|
+
# Use ffprobe to get FPS
|
|
132
|
+
cmd = [
|
|
133
|
+
'ffprobe',
|
|
134
|
+
'-v', 'error',
|
|
135
|
+
'-select_streams', 'v:0',
|
|
136
|
+
'-show_entries', 'stream=r_frame_rate',
|
|
137
|
+
'-of', 'csv=p=0',
|
|
138
|
+
str(video_path)
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
143
|
+
fps_str = result.stdout.strip()
|
|
144
|
+
|
|
145
|
+
# Handle fractional FPS (e.g., "30/1")
|
|
146
|
+
if '/' in fps_str:
|
|
147
|
+
num, den = fps_str.split('/')
|
|
148
|
+
return float(num) / float(den)
|
|
149
|
+
else:
|
|
150
|
+
return float(fps_str)
|
|
151
|
+
except subprocess.CalledProcessError:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Fallback to OpenCV if available
|
|
155
|
+
try:
|
|
156
|
+
import cv2
|
|
157
|
+
cap = cv2.VideoCapture(str(video_path))
|
|
158
|
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
159
|
+
cap.release()
|
|
160
|
+
|
|
161
|
+
if fps and fps > 0:
|
|
162
|
+
return float(fps)
|
|
163
|
+
except ImportError:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
raise RuntimeError(f"Could not determine FPS from video: {video_path}. Neither ffprobe nor OpenCV worked.")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def run_generate_kinematic_parameter(h5_path, video_path=None, fps_default=30.0):
|
|
170
|
+
"""
|
|
171
|
+
Run the generate_kinematic_parameter.py script on the .h5 file
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
h5_path (str or Path): Path to the .h5 file
|
|
175
|
+
video_path (str or Path, optional): Path to associated video file
|
|
176
|
+
fps_default (float): Default FPS to use if not found elsewhere
|
|
177
|
+
"""
|
|
178
|
+
import subprocess
|
|
179
|
+
import os
|
|
180
|
+
|
|
181
|
+
# Get the path to the generate_kinematic_parameter.py script in the workspace
|
|
182
|
+
script_path = Path(__file__).parent.parent / ".." / "generate_kinematic_parameter.py"
|
|
183
|
+
|
|
184
|
+
# Build the command
|
|
185
|
+
cmd = [
|
|
186
|
+
sys.executable, # Use the same Python interpreter
|
|
187
|
+
str(script_path),
|
|
188
|
+
"--h5", str(h5_path),
|
|
189
|
+
"--fps-default", str(fps_default)
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
# Add video path if provided
|
|
193
|
+
if video_path:
|
|
194
|
+
cmd.extend(["--video", str(video_path)])
|
|
195
|
+
else:
|
|
196
|
+
# Disable video search if no video path provided
|
|
197
|
+
cmd.append("--no-search-video")
|
|
198
|
+
|
|
199
|
+
# Execute the command
|
|
200
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
201
|
+
|
|
202
|
+
if result.returncode != 0:
|
|
203
|
+
raise RuntimeError(f"Failed to run generate_kinematic_parameter.py:\nSTDOUT: {result.stdout}\nSTDERR: {result.stderr}")
|
|
204
|
+
|
|
205
|
+
print(result.stdout)
|
|
206
|
+
if result.stderr:
|
|
207
|
+
print("STDERR:", result.stderr)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main():
|
|
211
|
+
parser = argparse.ArgumentParser(description='Check and generate KinematicParameter data in .h5 files')
|
|
212
|
+
parser.add_argument('--h5-path', required=True, help='Path to the .h5 file')
|
|
213
|
+
parser.add_argument('--fps-default', type=float, default=30.0, help='Default FPS if not found in H5 or video')
|
|
214
|
+
|
|
215
|
+
args = parser.parse_args()
|
|
216
|
+
|
|
217
|
+
h5_path = Path(args.h5_path)
|
|
218
|
+
|
|
219
|
+
if not h5_path.exists():
|
|
220
|
+
raise FileNotFoundError(f"H5 file does not exist: {h5_path}")
|
|
221
|
+
|
|
222
|
+
# Check if 2Dskeleton data exists
|
|
223
|
+
if not check_skeleton_data_exists(h5_path):
|
|
224
|
+
raise ValueError(f"No 2Dskeleton data found in {h5_path}")
|
|
225
|
+
|
|
226
|
+
# Check if KinematicParameter already exists
|
|
227
|
+
if check_kinematic_parameter_exists(h5_path):
|
|
228
|
+
print(f"KinematicParameter already exists in {h5_path}")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
print(f"KinematicParameter not found in {h5_path}, generating...")
|
|
232
|
+
|
|
233
|
+
# Try to get FPS from H5 file
|
|
234
|
+
fps = get_fps_from_h5(h5_path)
|
|
235
|
+
|
|
236
|
+
if fps is not None:
|
|
237
|
+
print(f"Using FPS from H5 file: {fps}")
|
|
238
|
+
# Run the generation script with the FPS from H5
|
|
239
|
+
run_generate_kinematic_parameter(h5_path, fps_default=fps)
|
|
240
|
+
else:
|
|
241
|
+
print("FPS not found in H5 file, looking for associated video...")
|
|
242
|
+
|
|
243
|
+
# Find associated video file
|
|
244
|
+
video_path = find_associated_video(h5_path)
|
|
245
|
+
|
|
246
|
+
if video_path:
|
|
247
|
+
print(f"Found associated video: {video_path}")
|
|
248
|
+
try:
|
|
249
|
+
fps = get_fps_from_video(video_path)
|
|
250
|
+
print(f"Determined FPS from video: {fps}")
|
|
251
|
+
# Run the generation script with the video
|
|
252
|
+
run_generate_kinematic_parameter(h5_path, video_path=video_path)
|
|
253
|
+
except RuntimeError as e:
|
|
254
|
+
print(f"Could not get FPS from video: {e}")
|
|
255
|
+
print(f"Using default FPS: {args.fps_default}")
|
|
256
|
+
run_generate_kinematic_parameter(h5_path, fps_default=args.fps_default)
|
|
257
|
+
else:
|
|
258
|
+
print(f"No associated video found. Using default FPS: {args.fps_default}")
|
|
259
|
+
run_generate_kinematic_parameter(h5_path, fps_default=args.fps_default)
|
|
260
|
+
|
|
261
|
+
print(f"Processing completed for {h5_path}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
main()
|