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