moseq2-app 1.4.0__tar.gz
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.
- moseq2_app-1.4.0/LICENSE.md +49 -0
- moseq2_app-1.4.0/PKG-INFO +131 -0
- moseq2_app-1.4.0/README.md +79 -0
- moseq2_app-1.4.0/moseq2_app/__init__.py +1 -0
- moseq2_app-1.4.0/moseq2_app/_yaml.py +73 -0
- moseq2_app-1.4.0/moseq2_app/flip/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/flip/train.py +139 -0
- moseq2_app-1.4.0/moseq2_app/flip/widget.py +291 -0
- moseq2_app-1.4.0/moseq2_app/gui/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/gui/progress.py +534 -0
- moseq2_app-1.4.0/moseq2_app/gui/widgets.py +116 -0
- moseq2_app-1.4.0/moseq2_app/gui/wrappers.py +160 -0
- moseq2_app-1.4.0/moseq2_app/main.py +275 -0
- moseq2_app-1.4.0/moseq2_app/roi/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/roi/controller.py +90 -0
- moseq2_app-1.4.0/moseq2_app/roi/validation.py +327 -0
- moseq2_app-1.4.0/moseq2_app/roi/view.py +93 -0
- moseq2_app-1.4.0/moseq2_app/roi/widget.py +597 -0
- moseq2_app-1.4.0/moseq2_app/scalars/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/scalars/controller.py +122 -0
- moseq2_app-1.4.0/moseq2_app/scalars/widgets.py +69 -0
- moseq2_app-1.4.0/moseq2_app/stat/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/stat/controller.py +502 -0
- moseq2_app-1.4.0/moseq2_app/stat/view.py +1328 -0
- moseq2_app-1.4.0/moseq2_app/stat/widgets.py +280 -0
- moseq2_app-1.4.0/moseq2_app/util.py +210 -0
- moseq2_app-1.4.0/moseq2_app/viz/__init__.py +0 -0
- moseq2_app-1.4.0/moseq2_app/viz/controller.py +753 -0
- moseq2_app-1.4.0/moseq2_app/viz/view.py +103 -0
- moseq2_app-1.4.0/moseq2_app/viz/widgets.py +271 -0
- moseq2_app-1.4.0/pyproject.toml +73 -0
- moseq2_app-1.4.0/tests/__init__.py +0 -0
- moseq2_app-1.4.0/tests/analysis/__init__.py +0 -0
- moseq2_app-1.4.0/tests/analysis/test_01_progress.py +202 -0
- moseq2_app-1.4.0/tests/analysis/test_02_model_folders.py +132 -0
- moseq2_app-1.4.0/tests/analysis/test_03_model_override.py +163 -0
- moseq2_app-1.4.0/tests/analysis/test_04_moseq_df.py +180 -0
- moseq2_app-1.4.0/tests/analysis/test_05_export_moseq_df.py +104 -0
- moseq2_app-1.4.0/tests/analysis/test_06_stats_df.py +194 -0
- moseq2_app-1.4.0/tests/analysis/test_07_export_stats_df.py +97 -0
- moseq2_app-1.4.0/tests/analysis/test_08_fingerprint.py +213 -0
- moseq2_app-1.4.0/tests/analysis/test_09_label_syllables.py +308 -0
- moseq2_app-1.4.0/tests/analysis/test_10_dendrogram.py +150 -0
- moseq2_app-1.4.0/tests/analysis/test_11_syllable_stats/test_which_syllables_differ_between_the_groups_is_unchanged.yml +10 -0
- moseq2_app-1.4.0/tests/analysis/test_11_syllable_stats.py +190 -0
- moseq2_app-1.4.0/tests/analysis/test_12_transition_matrices.py +245 -0
- moseq2_app-1.4.0/tests/analysis/test_13_export_transition_matrices.py +149 -0
- moseq2_app-1.4.0/tests/analysis/test_14_transition_graph.py +149 -0
- moseq2_app-1.4.0/tests/conftest.py +602 -0
- moseq2_app-1.4.0/tests/extract_modeling/__init__.py +0 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_01_versions.py +163 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_02_progress.py +264 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_03_config/test_generated_config_is_unchanged.yml +90 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_03_config.py +154 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_04_flip_classifier.py +208 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_05_arena_mask/test_detected_parameters_are_unchanged.yml +87 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_05_arena_mask.py +399 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_06_extract.py +463 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_07_validate.py +163 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_08_preview.py +127 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_09_aggregate.py +160 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_10_groups.py +225 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_11_scalars.py +144 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_12_uuid_lookup.py +128 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_13_pca.py +179 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_14_pca_figures.py +88 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_15_scores.py +140 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_16_changepoints.py +133 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_17_model_settings.py +175 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_18_model.py +193 -0
- moseq2_app-1.4.0/tests/extract_modeling/test_19_best_fit.py +173 -0
- moseq2_app-1.4.0/tests/fixture_registry.txt +2 -0
- moseq2_app-1.4.0/tests/fixtures.py +80 -0
- moseq2_app-1.4.0/tests/video.py +36 -0
- moseq2_app-1.4.0/tests/widgets.py +106 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Non-Commercial Research and Academic Use Software License and Terms of Use
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
MoSeq is a software package that includes original code created by the Harvard researchers listed below (the “Software”), and third-party code that may be obtained by End Users separately. The Software is designed to label behaviors identified from 3D pose dynamics data through the use of computational modeling and fitting approaches. The Software was developed by Alexander Wiltschko, Ph.D., Sandeep Robert Datta, Ph.D., and Matthew J. Johnson, Ph.D. at Harvard University. It is distributed for free academic and non-commercial research use by the President and Fellows of Harvard College (“Harvard”).
|
|
5
|
+
|
|
6
|
+
Using the Software indicates your agreement to be bound by the terms of this Software Use Agreement (“Agreement”). Absent your agreement to the terms below, you (the “End User”) have no rights to hold or use the Software whatsoever.
|
|
7
|
+
|
|
8
|
+
Harvard agrees to grant hereunder a limited non-exclusive license to End User for the use of the Software in the performance of End User’s internal, non-commercial research and academic use at End User’s academic or not-for-profit research institution (“Institution”) on the following terms and conditions:
|
|
9
|
+
|
|
10
|
+
1. **NO COMMERCIAL USE.** End User shall not use the Software for Commercial use and any such use of the Software is expressly prohibited. “Commercial use” includes, but is not limited to, (i) use of the Software in fee-for-service arrangements, (ii) use of the Software by core facilities or laboratories to provide research services to (or in collaboration with) for-profit third parties for a fee, and (iii) use of the Software in industry-sponsored and/or collaborative research projects in which any commercial rights are granted to the sponsor or collaborator. If End User wishes to use the Software for Commercial use, End User must execute a separate license agreement with Harvard.
|
|
11
|
+
|
|
12
|
+
_Requests for use of the Software for Commercial use, please contact:_
|
|
13
|
+
|
|
14
|
+
Office of Technology Development
|
|
15
|
+
Harvard University
|
|
16
|
+
Smith Campus Center, Suite 727E
|
|
17
|
+
1350 Massachusetts Avenue Cambridge, MA 02138 USA Telephone: (617) 495-3067
|
|
18
|
+
Facsimile: (617) 495-9568
|
|
19
|
+
E-mail: otd@harvard.edu
|
|
20
|
+
|
|
21
|
+
2. **OWNERSHIP AND COPYRIGHT NOTICE.** Harvard owns all intellectual property in the Software. End User shall gain no ownership to the Software. End User shall not remove or delete, and shall retain in the Software (including in any modifications to the Software and in any Derivative Works), the copyright, trademark, or other notices pertaining to Software as provided with the Software.
|
|
22
|
+
|
|
23
|
+
3. **DERIVATIVE WORKS.** End User may create use and distribute Derivative Works, as such term is defined under U.S. copyright laws, provided that any such Derivative Works shall be restricted to non-commercial research and academic use. End User may not distribute Derivative Works to any for-profit third parties for Commercial use.
|
|
24
|
+
|
|
25
|
+
4. **FEEDBACK.** In order to improve the Software, comments from End Users may be useful. End User agrees to provide Harvard with feedback on the End User’s use of the Software (e.g., any bugs in the Software, the user experience, etc.). Harvard is permitted to use such information provided by End User in making changes and improvements to the Software without compensation or accounting to End User.
|
|
26
|
+
|
|
27
|
+
5. **NON ASSERT.** End User acknowledges that Harvard may develop modifications to the Software that may be based on the feedback provided by End User under Section 5 above. Harvard shall not be restricted in any way by End User regarding its use of such information. End User acknowledges the right of Harvard to prepare, publish, display, reproduce, transmit and or use modifications to the Software that may be substantially similar or functionally equivalent to End User’s modifications and/or improvements if any. In the event that End User obtains patent protection for any modification or improvement to Software, End User agrees not to allege or enjoin infringement of End User’s patent against Harvard, or any of the researchers, medical or research staff, officers, directors and employees of those institutions.
|
|
28
|
+
|
|
29
|
+
6. **PUBLICATION & ATTRIBUTION.** End User has the right to publish, present, or share results from the use of the Software. In accordance with customary academic practice, End User will acknowledge Harvard as the provider of the Software and may cite the relevant reference(s) from the following list of publications:
|
|
30
|
+
|
|
31
|
+
Wiltschko AB, Johnson MJ, Iurilli G, Peterson RE, Katon JE, Pashkovski SL, Abraira VE, Adams RP and Datta SR. (2015). Mapping sub-second Structure in Behavior. Neuron, 88:1121.
|
|
32
|
+
|
|
33
|
+
Markowitz JE, Gillis WF, Beron CC, Neufeld SQ, Robertson K, Bhagat ND, Peterson RE, Peterson E, Hyun M, Linderman SW, Sabatini BL and Datta SR. (2018). The striatum organizes 3D behavior via moment-to-moment action selection. Cell, in press.
|
|
34
|
+
|
|
35
|
+
7.** NO WARRANTIES.** THE SOFTWARE IS PROVIDED "AS IS." TO THE FULLEST EXTENT PERMITTED BY LAW, HARVARD HEREBY DISCLAIMS ALL WARRANTIES OF ANY KIND (EXPRESS, IMPLIED OR OTHERWISE) REGARDING THE SOFTWARE, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OWNERSHIP, AND NON-INFRINGEMENT. HARVARD MAKES NO WARRANTY ABOUT THE ACCURACY, RELIABILITY, COMPLETENESS, TIMELINESS, SUFFICIENCY OR QUALITY OF THE SOFTWARE. HARVARD DOES NOT WARRANT THAT THE SOFTWARE WILL OPERATE WITHOUT ERROR OR INTERRUPTION.
|
|
36
|
+
|
|
37
|
+
8. **LIMITATIONS OF LIABILITY AND REMEDIES.** USE OF THE SOFTWARE IS AT END USER’S OWN RISK. IF END USER IS DISSATISFIED WITH THE SOFTWARE, ITS EXCLUSIVE REMEDY IS TO STOP USING IT. IN NO EVENT SHALL HARVARD BE LIABLE TO END USER OR ITS INSTITUTION, IN CONTRACT, TORT OR OTHERWISE, FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR OTHER DAMAGES OF ANY KIND WHATSOEVER ARISING OUT OF OR IN CONNECTION WITH THE SOFTWARE, EVEN IF HARVARD IS NEGLIGENT OR OTHERWISE AT FAULT, AND REGARDLESS OF WHETHER HARVARD IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
38
|
+
|
|
39
|
+
9. **INDEMNIFICATION.** To the extent permitted by law, End User shall indemnify, defend and hold harmless Harvard, their corporate affiliates, current or future directors, trustees, officers, faculty, medical and professional staff, employees, students and agents and their respective successors, heirs and assigns (the "Indemnitees"), against any liability, damage, loss or expense (including reasonable attorney's fees and expenses of litigation) incurred by or imposed upon the Indemnitees or any one of them in connection with any claims, suits, actions, demands or judgments arising from End User’s breach of this Agreement or its Institution’s use of the Software except to the extent caused by the gross negligence or willful misconduct of Harvard. This indemnification provision shall survive expiration or termination of this Agreement.
|
|
40
|
+
|
|
41
|
+
10. **GOVERNING LAW.** This Agreement shall be construed and governed by the laws of the Commonwealth of Massachusetts regardless of otherwise applicable choice of law standards.
|
|
42
|
+
|
|
43
|
+
11. **NON-USE OF NAME.** Nothing in this License and Terms of Use shall be construed as granting End Users or their Institutions any rights or licenses to use any trademarks, service marks or logos associated with the Software. End User may not use the terms “Harvard” (or a substantially similar term) in any way that is inconsistent with the permitted uses described herein. End Users may not use any name or emblem of Harvard or any of its schools or subdivisions for any purpose, or to falsely suggest any relationship between End User (or its Institution) and Harvard, or in any manner that would infringe or violate any of its rights.
|
|
44
|
+
|
|
45
|
+
12. End User represents and warrants that it has the legal authority to enter into this License and Terms of Use on behalf of itself and its Institution.
|
|
46
|
+
|
|
47
|
+
***
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moseq2-app
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Interactive data exploration tools for the MoSeq2 pipeline.
|
|
5
|
+
Author: Datta Lab
|
|
6
|
+
License-Expression: LicenseRef-Harvard-Academic-Non-Commercial
|
|
7
|
+
License-File: LICENSE.md
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: bokeh>=2.4.2
|
|
19
|
+
Requires-Dist: fastparquet>=0.4.1
|
|
20
|
+
Requires-Dist: h5py>=2.10.0
|
|
21
|
+
Requires-Dist: holoviews>=1.14.7
|
|
22
|
+
Requires-Dist: ipython>=7.14.0
|
|
23
|
+
Requires-Dist: ipywidgets>=7.5.1
|
|
24
|
+
Requires-Dist: jinja2>=3.0.1
|
|
25
|
+
Requires-Dist: joblib>=0.15.1
|
|
26
|
+
Requires-Dist: jupyter-bokeh>=2.0.3
|
|
27
|
+
Requires-Dist: jupyter>=1.0.0
|
|
28
|
+
Requires-Dist: matplotlib>=3.1.2
|
|
29
|
+
Requires-Dist: numpy>=1.18.3
|
|
30
|
+
Requires-Dist: opencv-python>=4.1.2.30
|
|
31
|
+
Requires-Dist: pandas>=1.0.5
|
|
32
|
+
Requires-Dist: panel>=0.12.6
|
|
33
|
+
Requires-Dist: plotly>=4.14.3
|
|
34
|
+
Requires-Dist: qgridnext>=2.0.4
|
|
35
|
+
Requires-Dist: ruamel.yaml>=0.16.5
|
|
36
|
+
Requires-Dist: scikit-image>=0.16.2
|
|
37
|
+
Requires-Dist: scikit-learn>=0.20.3
|
|
38
|
+
Requires-Dist: scipy>=1.3.2
|
|
39
|
+
Requires-Dist: seaborn>=0.11.0
|
|
40
|
+
Requires-Dist: moseq2-extract<2,>=1.4.0
|
|
41
|
+
Requires-Dist: moseq2-model<2,>=1.4.0
|
|
42
|
+
Requires-Dist: moseq2-pca<2,>=1.4.0
|
|
43
|
+
Requires-Dist: moseq2-viz<2,>=1.4.0
|
|
44
|
+
Requires-Dist: Cython>=3.0.11
|
|
45
|
+
Requires-Dist: requests>=2.32.3
|
|
46
|
+
Provides-Extra: tests
|
|
47
|
+
Requires-Dist: pytest>=8.3.2; extra == "tests"
|
|
48
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "tests"
|
|
49
|
+
Requires-Dist: pytest-regressions>=2.5.0; extra == "tests"
|
|
50
|
+
Requires-Dist: pooch>=1.8.2; extra == "tests"
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
<!-- [](https://app.travis-ci.com/dattalab/moseq2-app) -->
|
|
54
|
+
<p align="center">
|
|
55
|
+
<img src="https://drive.google.com/uc?export=view&id=1oc0_0mlN0VTZEPMQTg_hnYAC87Lb58MI" />
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
Welcome to the `moseq2-app` repository. Motion Sequencing (MoSeq) is an unsupervised machine learning method used to describe mouse behavior and `moseq2-app` is the starting point to MoSeq2 package suite.
|
|
59
|
+
|
|
60
|
+
To get started, install it with `pip install moseq2-app` and head over to the [wiki](https://github.com/dattalab/moseq2-app/wiki/MoSeq2-Installation) for the full procedure and documentation for the MoSeq2 package suite.
|
|
61
|
+
|
|
62
|
+
**Quick Links:**
|
|
63
|
+
[](https://moseqworkspace.slack.com)
|
|
64
|
+
[](https://github.com/dattalab/moseq2-app/wiki)
|
|
65
|
+
[](https://zenodo.org/badge/latestdoi/204555901)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Overview
|
|
69
|
+
|
|
70
|
+
<!---Adapted from
|
|
71
|
+
Datta, Sandeep Robert, David J. Anderson, Kristin Branson, Pietro Perona, and Andrew Leifer. 2019. “Computational Neuroethology: A Call to Action.” Neuron 104 (1): 11–24.
|
|
72
|
+
-->
|
|
73
|
+
**MoSeq takes 3D depth videos as input (obtained using commercially-available sensors) and then uses statistical learning techniques to identify the components of mouse body language.** This is achieved by fitting an autoregressive hidden Markov model that parses behavior into a set of sub-second motifs called syllables. This segmentation naturally yields boundaries between syllables, and therefore also reveals the structure that governs the interconnections between syllables over time, which we refer to as behavioral grammar (see [Wiltschko et al., 2015](https://www.sciencedirect.com/science/article/pii/S0896627315010375) for the first description of MoSeq).
|
|
74
|
+
|
|
75
|
+
Because MoSeq relies on unsupervised machine learning, MoSeq discovers the set of syllables and grammar expressed in any given experiment. By combining MoSeq with electrophysiology, multi-color photometry, and miniscope methods, neural correlates for 3D behavioral syllables have recently been identified in the dorsolateral striatum (DLS) ([Markowitz et al., 2018](https://www.sciencedirect.com/science/article/pii/S0092867418305129)). Furthermore, MoSeq has been combined with optogenetic stimulation to reveal the differential consequences of activating the motor cortex, the dorsal striatum, and the ventral striatum ([Pisanello et al.,2017](https://www.nature.com/articles/nn.4591); [Wiltschko et al., 2015](https://www.sciencedirect.com/science/article/pii/S0896627315010375)). These results are consistent with similar results recently obtained using marker-based approaches to explore the relationship between 3D posture and activity in the posterior parietal cortex ([Mimica et al., 2018](https://www.science.org/doi/10.1126/science.aau2013)).
|
|
76
|
+
|
|
77
|
+
There are two basic steps to MoSeq, and this GitHub repository and the wiki supports both. First, 3D data need to be acquired. Here you will find instructions for assembling a standard MoSeq data acquisition platform, and code to acquire 3D videos of mice as they freely behave. Second, these 3D data need to be modeled. We provide several different methods for modeling the data using MoSeq, which are compared below. We continue development of MoSeq and plan to incorporate additional features in the near future.
|
|
78
|
+
|
|
79
|
+
## MoSeq2 package suite
|
|
80
|
+
The MoSeq2 toolkit enables users to model mouse behavior across different experimental groups, and measure the differences between their behavior usages, durations, transition patterns. etc.
|
|
81
|
+
|
|
82
|
+
This package contains functionalities that can be used interactively in jupyter notebooks.
|
|
83
|
+
We provide a series of Jupyter Notebooks that cover the entire MoSeq pipeline to process their depth videos of mice, and segment their behavior into what is denoted as "syllables". In addition to the Jupyter notebooks, MoSeq has a Command Line Interface. Consult the wiki page for more detailed documentation of the MoSeq pipeline [here](https://github.com/dattalab/moseq2-app/wiki).
|
|
84
|
+
|
|
85
|
+
# Getting Started
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install moseq2-app
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Python 3.10 or newer. Verified on Linux; macOS and Windows support is being
|
|
92
|
+
verified. Installing `moseq2-app` brings the rest of the suite with it:
|
|
93
|
+
`moseq2-extract`, `moseq2-pca`, `moseq2-model` and `moseq2-viz`, along with
|
|
94
|
+
their command line interfaces.
|
|
95
|
+
|
|
96
|
+
**ffmpeg is a separate requirement.** `moseq2-extract` reads and writes depth
|
|
97
|
+
video by calling `ffmpeg` and `ffprobe` and expects to find them on your
|
|
98
|
+
`PATH`. No Python install provides them, so install them yourself with conda,
|
|
99
|
+
apt or Homebrew.
|
|
100
|
+
|
|
101
|
+
**The notebooks are not part of the package.** Download the two that make up
|
|
102
|
+
the pipeline and put them in the parent directory of your data, which is what
|
|
103
|
+
lets the interactive video widgets load:
|
|
104
|
+
|
|
105
|
+
- [MoSeq2-Extract-Modeling-Notebook.ipynb](https://raw.githubusercontent.com/dattalab/moseq2-app/release/notebooks/MoSeq2-Extract-Modeling-Notebook.ipynb)
|
|
106
|
+
- [MoSeq2-Analysis-Visualization-Notebook.ipynb](https://raw.githubusercontent.com/dattalab/moseq2-app/release/notebooks/MoSeq2-Analysis-Visualization-Notebook.ipynb)
|
|
107
|
+
|
|
108
|
+
### **[The installation guide](https://github.com/dattalab/moseq2-app/wiki/MoSeq2-Installation) carries the whole procedure, including how to launch Jupyter so the notebooks' video widgets work.**
|
|
109
|
+
|
|
110
|
+
You can find more information about the MoSeq2 package suite, the acquisition and analysis pipeline, documentation for Command Line Interface (CLI), tutorials, etc. in the [wiki](https://github.com/dattalab/moseq2-app/wiki). If you want to explore MoSeq functionalities, check out the guide for [downloading test data](https://github.com/dattalab/moseq2-app/wiki/Download-Test-Data).
|
|
111
|
+
|
|
112
|
+
# Community Support and Contributing
|
|
113
|
+
- Please join [](https://moseqworkspace.slack.com) to post questions and interactive with MoSeq developers and users.
|
|
114
|
+
- If you encounter bugs, errors or issues, please submit a Bug report [here](https://github.com/dattalab/moseq2-app/issues/new/choose). We encourage you to check out the [troubleshooting and tips section](https://github.com/dattalab/moseq2-app/wiki/Troubleshooting-and-Tips) and search your issues in [the existing issues](https://github.com/dattalab/moseq2-app/issues) first.
|
|
115
|
+
- If you want to see certain features in MoSeq or you have new ideas, please submit a Feature request [here](https://github.com/dattalab/moseq2-app/issues/new/choose).
|
|
116
|
+
- If you want to contribute to our codebases, please check out our [Developer Guidelines](https://github.com/dattalab/moseq2-app/wiki/MoSeq-Developer-Guidelines).
|
|
117
|
+
- Please tell us what you think by filling out [this user survey](https://forms.gle/S88jptAEs41mQjff7).
|
|
118
|
+
|
|
119
|
+
# Versions
|
|
120
|
+
- [Changelog](https://github.com/dattalab/moseq2-app/wiki/Changelog)
|
|
121
|
+
|
|
122
|
+
# Publications
|
|
123
|
+
- [Mapping Sub-Second Structure in Mouse Behavior](http://datta.hms.harvard.edu/wp-content/uploads/2018/01/pub_23.pdf)
|
|
124
|
+
- [The Striatum Organizes 3D Behavior via Moment-to-Moment Action Selection](http://datta.hms.harvard.edu/wp-content/uploads/2019/06/Markowitz.final_.pdf)
|
|
125
|
+
- [Q&A: Understanding the composition of behavior](http://datta.hms.harvard.edu/wp-content/uploads/2019/06/Datta-QA.pdf)
|
|
126
|
+
- [Computational Neuroethology: A Call to Action]("http://datta.hms.harvard.edu/wp-content/uploads/2020/01/AC2A.pdf")
|
|
127
|
+
- [Revealing the structure of pharmacobehavioral space through motion sequencing](https://www.nature.com/articles/s41593-020-00706-3)
|
|
128
|
+
|
|
129
|
+
# License
|
|
130
|
+
|
|
131
|
+
MoSeq is freely available for academic use under a license provided by Harvard University. Please refer to the license file for details. If you are interested in using MoSeq for commercial purposes please contact Bob Datta directly at srdatta@hms.harvard.edu, who will put you in touch with the appropriate people in the Harvard Technology Transfer office.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!-- [](https://app.travis-ci.com/dattalab/moseq2-app) -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<img src="https://drive.google.com/uc?export=view&id=1oc0_0mlN0VTZEPMQTg_hnYAC87Lb58MI" />
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
Welcome to the `moseq2-app` repository. Motion Sequencing (MoSeq) is an unsupervised machine learning method used to describe mouse behavior and `moseq2-app` is the starting point to MoSeq2 package suite.
|
|
7
|
+
|
|
8
|
+
To get started, install it with `pip install moseq2-app` and head over to the [wiki](https://github.com/dattalab/moseq2-app/wiki/MoSeq2-Installation) for the full procedure and documentation for the MoSeq2 package suite.
|
|
9
|
+
|
|
10
|
+
**Quick Links:**
|
|
11
|
+
[](https://moseqworkspace.slack.com)
|
|
12
|
+
[](https://github.com/dattalab/moseq2-app/wiki)
|
|
13
|
+
[](https://zenodo.org/badge/latestdoi/204555901)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Overview
|
|
17
|
+
|
|
18
|
+
<!---Adapted from
|
|
19
|
+
Datta, Sandeep Robert, David J. Anderson, Kristin Branson, Pietro Perona, and Andrew Leifer. 2019. “Computational Neuroethology: A Call to Action.” Neuron 104 (1): 11–24.
|
|
20
|
+
-->
|
|
21
|
+
**MoSeq takes 3D depth videos as input (obtained using commercially-available sensors) and then uses statistical learning techniques to identify the components of mouse body language.** This is achieved by fitting an autoregressive hidden Markov model that parses behavior into a set of sub-second motifs called syllables. This segmentation naturally yields boundaries between syllables, and therefore also reveals the structure that governs the interconnections between syllables over time, which we refer to as behavioral grammar (see [Wiltschko et al., 2015](https://www.sciencedirect.com/science/article/pii/S0896627315010375) for the first description of MoSeq).
|
|
22
|
+
|
|
23
|
+
Because MoSeq relies on unsupervised machine learning, MoSeq discovers the set of syllables and grammar expressed in any given experiment. By combining MoSeq with electrophysiology, multi-color photometry, and miniscope methods, neural correlates for 3D behavioral syllables have recently been identified in the dorsolateral striatum (DLS) ([Markowitz et al., 2018](https://www.sciencedirect.com/science/article/pii/S0092867418305129)). Furthermore, MoSeq has been combined with optogenetic stimulation to reveal the differential consequences of activating the motor cortex, the dorsal striatum, and the ventral striatum ([Pisanello et al.,2017](https://www.nature.com/articles/nn.4591); [Wiltschko et al., 2015](https://www.sciencedirect.com/science/article/pii/S0896627315010375)). These results are consistent with similar results recently obtained using marker-based approaches to explore the relationship between 3D posture and activity in the posterior parietal cortex ([Mimica et al., 2018](https://www.science.org/doi/10.1126/science.aau2013)).
|
|
24
|
+
|
|
25
|
+
There are two basic steps to MoSeq, and this GitHub repository and the wiki supports both. First, 3D data need to be acquired. Here you will find instructions for assembling a standard MoSeq data acquisition platform, and code to acquire 3D videos of mice as they freely behave. Second, these 3D data need to be modeled. We provide several different methods for modeling the data using MoSeq, which are compared below. We continue development of MoSeq and plan to incorporate additional features in the near future.
|
|
26
|
+
|
|
27
|
+
## MoSeq2 package suite
|
|
28
|
+
The MoSeq2 toolkit enables users to model mouse behavior across different experimental groups, and measure the differences between their behavior usages, durations, transition patterns. etc.
|
|
29
|
+
|
|
30
|
+
This package contains functionalities that can be used interactively in jupyter notebooks.
|
|
31
|
+
We provide a series of Jupyter Notebooks that cover the entire MoSeq pipeline to process their depth videos of mice, and segment their behavior into what is denoted as "syllables". In addition to the Jupyter notebooks, MoSeq has a Command Line Interface. Consult the wiki page for more detailed documentation of the MoSeq pipeline [here](https://github.com/dattalab/moseq2-app/wiki).
|
|
32
|
+
|
|
33
|
+
# Getting Started
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install moseq2-app
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Python 3.10 or newer. Verified on Linux; macOS and Windows support is being
|
|
40
|
+
verified. Installing `moseq2-app` brings the rest of the suite with it:
|
|
41
|
+
`moseq2-extract`, `moseq2-pca`, `moseq2-model` and `moseq2-viz`, along with
|
|
42
|
+
their command line interfaces.
|
|
43
|
+
|
|
44
|
+
**ffmpeg is a separate requirement.** `moseq2-extract` reads and writes depth
|
|
45
|
+
video by calling `ffmpeg` and `ffprobe` and expects to find them on your
|
|
46
|
+
`PATH`. No Python install provides them, so install them yourself with conda,
|
|
47
|
+
apt or Homebrew.
|
|
48
|
+
|
|
49
|
+
**The notebooks are not part of the package.** Download the two that make up
|
|
50
|
+
the pipeline and put them in the parent directory of your data, which is what
|
|
51
|
+
lets the interactive video widgets load:
|
|
52
|
+
|
|
53
|
+
- [MoSeq2-Extract-Modeling-Notebook.ipynb](https://raw.githubusercontent.com/dattalab/moseq2-app/release/notebooks/MoSeq2-Extract-Modeling-Notebook.ipynb)
|
|
54
|
+
- [MoSeq2-Analysis-Visualization-Notebook.ipynb](https://raw.githubusercontent.com/dattalab/moseq2-app/release/notebooks/MoSeq2-Analysis-Visualization-Notebook.ipynb)
|
|
55
|
+
|
|
56
|
+
### **[The installation guide](https://github.com/dattalab/moseq2-app/wiki/MoSeq2-Installation) carries the whole procedure, including how to launch Jupyter so the notebooks' video widgets work.**
|
|
57
|
+
|
|
58
|
+
You can find more information about the MoSeq2 package suite, the acquisition and analysis pipeline, documentation for Command Line Interface (CLI), tutorials, etc. in the [wiki](https://github.com/dattalab/moseq2-app/wiki). If you want to explore MoSeq functionalities, check out the guide for [downloading test data](https://github.com/dattalab/moseq2-app/wiki/Download-Test-Data).
|
|
59
|
+
|
|
60
|
+
# Community Support and Contributing
|
|
61
|
+
- Please join [](https://moseqworkspace.slack.com) to post questions and interactive with MoSeq developers and users.
|
|
62
|
+
- If you encounter bugs, errors or issues, please submit a Bug report [here](https://github.com/dattalab/moseq2-app/issues/new/choose). We encourage you to check out the [troubleshooting and tips section](https://github.com/dattalab/moseq2-app/wiki/Troubleshooting-and-Tips) and search your issues in [the existing issues](https://github.com/dattalab/moseq2-app/issues) first.
|
|
63
|
+
- If you want to see certain features in MoSeq or you have new ideas, please submit a Feature request [here](https://github.com/dattalab/moseq2-app/issues/new/choose).
|
|
64
|
+
- If you want to contribute to our codebases, please check out our [Developer Guidelines](https://github.com/dattalab/moseq2-app/wiki/MoSeq-Developer-Guidelines).
|
|
65
|
+
- Please tell us what you think by filling out [this user survey](https://forms.gle/S88jptAEs41mQjff7).
|
|
66
|
+
|
|
67
|
+
# Versions
|
|
68
|
+
- [Changelog](https://github.com/dattalab/moseq2-app/wiki/Changelog)
|
|
69
|
+
|
|
70
|
+
# Publications
|
|
71
|
+
- [Mapping Sub-Second Structure in Mouse Behavior](http://datta.hms.harvard.edu/wp-content/uploads/2018/01/pub_23.pdf)
|
|
72
|
+
- [The Striatum Organizes 3D Behavior via Moment-to-Moment Action Selection](http://datta.hms.harvard.edu/wp-content/uploads/2019/06/Markowitz.final_.pdf)
|
|
73
|
+
- [Q&A: Understanding the composition of behavior](http://datta.hms.harvard.edu/wp-content/uploads/2019/06/Datta-QA.pdf)
|
|
74
|
+
- [Computational Neuroethology: A Call to Action]("http://datta.hms.harvard.edu/wp-content/uploads/2020/01/AC2A.pdf")
|
|
75
|
+
- [Revealing the structure of pharmacobehavioral space through motion sequencing](https://www.nature.com/articles/s41593-020-00706-3)
|
|
76
|
+
|
|
77
|
+
# License
|
|
78
|
+
|
|
79
|
+
MoSeq is freely available for academic use under a license provided by Harvard University. Please refer to the license file for details. If you are interested in using MoSeq for commercial purposes please contact Bob Datta directly at srdatta@hms.harvard.edu, who will put you in touch with the appropriate people in the Harvard Technology Transfer office.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.0"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""YAML reading and writing, in one place.
|
|
2
|
+
|
|
3
|
+
``ruamel.yaml`` removed the module-level ``safe_load``/``safe_dump`` functions in
|
|
4
|
+
0.18 in favour of instantiating a parser. Routing every call through here keeps
|
|
5
|
+
that detail in one file rather than at each of the call sites.
|
|
6
|
+
|
|
7
|
+
The parser stays ``ruamel``, deliberately. ``ruamel``'s safe loader is YAML 1.2;
|
|
8
|
+
PyYAML is YAML 1.1 and reads unquoted ``yes``/``no``/``on``/``off`` as booleans
|
|
9
|
+
and ``06:00:00`` as a sexagesimal integer. moseq2 config and index files contain
|
|
10
|
+
both shapes, so switching parsers would quietly change how existing user files
|
|
11
|
+
are read.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import io
|
|
15
|
+
|
|
16
|
+
from ruamel.yaml import YAML
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parser():
|
|
20
|
+
"""A YAML parser configured the way ``safe_dump`` was.
|
|
21
|
+
|
|
22
|
+
A new one per call, because a ``YAML`` instance carries a serializer across
|
|
23
|
+
calls: if a value cannot be represented, the failed call leaves that
|
|
24
|
+
serializer open and every later dump writes nothing at all, silently.
|
|
25
|
+
Three of the four callers here move their output over a file the user
|
|
26
|
+
already has, so a dumper that quietly produces nothing destroys data.
|
|
27
|
+
|
|
28
|
+
``pure=True`` selects the Python implementation, matching what the
|
|
29
|
+
module-level functions used.
|
|
30
|
+
|
|
31
|
+
The two assignments restore emitter behaviour that ``YAML()`` does not
|
|
32
|
+
share with ``safe_dump``. ``allow_unicode`` escapes non-ASCII rather than
|
|
33
|
+
writing it literally, which also keeps a write from depending on the
|
|
34
|
+
locale encoding of the file handle. ``width`` is 80 in both, but the two
|
|
35
|
+
versions measure the column at which a flow sequence may break two
|
|
36
|
+
characters apart, so 82 here reproduces where ``safe_dump`` put its line
|
|
37
|
+
breaks -- both for the two-element ``path: [x.h5, x.yaml]`` entries that
|
|
38
|
+
fill an index file and for sequences long enough to wrap.
|
|
39
|
+
|
|
40
|
+
One emitter difference is left, and no configuration reaches it: where a
|
|
41
|
+
flow sequence does wrap, this version writes a space before the break. The
|
|
42
|
+
breaks land in the same places, so the files moseq2 writes -- the session
|
|
43
|
+
index, the project and per-session configs -- come out byte for byte as
|
|
44
|
+
they did.
|
|
45
|
+
"""
|
|
46
|
+
parser = YAML(typ="safe", pure=True)
|
|
47
|
+
parser.allow_unicode = False
|
|
48
|
+
parser.width = 82
|
|
49
|
+
return parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def read_yaml(path_or_stream):
|
|
53
|
+
"""Parse a YAML file, given a path or an open stream."""
|
|
54
|
+
if hasattr(path_or_stream, "read"):
|
|
55
|
+
return _parser().load(path_or_stream)
|
|
56
|
+
with open(path_or_stream, "r") as handle:
|
|
57
|
+
return _parser().load(handle)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def write_yaml(data, path_or_stream):
|
|
61
|
+
"""Write ``data`` as YAML, given a path or an open stream."""
|
|
62
|
+
if hasattr(path_or_stream, "write"):
|
|
63
|
+
_parser().dump(data, path_or_stream)
|
|
64
|
+
return
|
|
65
|
+
with open(path_or_stream, "w") as handle:
|
|
66
|
+
_parser().dump(data, handle)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dump_yaml(data):
|
|
70
|
+
"""Serialize ``data`` to a YAML string."""
|
|
71
|
+
stream = io.StringIO()
|
|
72
|
+
_parser().dump(data, stream)
|
|
73
|
+
return stream.getvalue()
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import h5py
|
|
3
|
+
import pickle
|
|
4
|
+
import joblib
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from sklearn.svm import SVC
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional, Tuple
|
|
10
|
+
from sklearn.decomposition import PCA
|
|
11
|
+
from sklearn.pipeline import make_pipeline
|
|
12
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
13
|
+
from moseq2_extract.extract.proc import clean_frames
|
|
14
|
+
from sklearn.model_selection import cross_val_score, KFold
|
|
15
|
+
from sklearn.preprocessing import StandardScaler, FunctionTransformer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CleanParameters:
|
|
20
|
+
prefilter_space: tuple = (3,)
|
|
21
|
+
prefilter_time: Optional[tuple] = None
|
|
22
|
+
strel_tail: Tuple[int, int] = (9, 9)
|
|
23
|
+
strel_min: Tuple[int, int] = (5, 5)
|
|
24
|
+
iters_tail: Optional[int] = 1
|
|
25
|
+
iters_min: Optional[int] = None
|
|
26
|
+
height_threshold: int = 10
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_training_dataset(
|
|
30
|
+
data_index_path: str, clean_parameters: Optional[CleanParameters] = None
|
|
31
|
+
) -> str:
|
|
32
|
+
np.random.seed(0)
|
|
33
|
+
data_index_path = Path(data_index_path)
|
|
34
|
+
out_path = data_index_path.with_name("training_data.npz")
|
|
35
|
+
if clean_parameters is None:
|
|
36
|
+
clean_parameters = CleanParameters()
|
|
37
|
+
|
|
38
|
+
# load trainingdata index
|
|
39
|
+
with open(data_index_path, "rb") as f:
|
|
40
|
+
session_paths, data_index = pickle.load(f)
|
|
41
|
+
|
|
42
|
+
# load frames
|
|
43
|
+
frames = []
|
|
44
|
+
for k, v in data_index.items():
|
|
45
|
+
with h5py.File(session_paths[k], "r") as h5f:
|
|
46
|
+
for left, _slice in v:
|
|
47
|
+
frames_subset = h5f["frames"][_slice]
|
|
48
|
+
if left:
|
|
49
|
+
frames_subset = np.rot90(frames_subset, 2, axes=(1, 2))
|
|
50
|
+
frames.append(frames_subset)
|
|
51
|
+
frames = np.concatenate(frames, axis=0)
|
|
52
|
+
# rotate frames
|
|
53
|
+
frames = np.concatenate((frames, np.rot90(frames, 2, axes=(1, 2))), axis=0)
|
|
54
|
+
|
|
55
|
+
flipped = np.zeros((len(frames),), dtype=np.uint8)
|
|
56
|
+
flipped[len(frames) // 2 :] = 1
|
|
57
|
+
|
|
58
|
+
# add some randomly shifted frames
|
|
59
|
+
shifts = np.random.randint(-5, 5, size=(len(frames), 2))
|
|
60
|
+
shifted_frames = np.array([np.roll(f, tuple(s), axis=(0, 1)) for f, s in zip(frames, shifts)]).astype(np.uint8)
|
|
61
|
+
|
|
62
|
+
# remove noise from frames
|
|
63
|
+
cleaned_frames = clean_frames(
|
|
64
|
+
np.where(frames > clean_parameters.height_threshold, frames, 0),
|
|
65
|
+
clean_parameters.prefilter_space,
|
|
66
|
+
clean_parameters.prefilter_time,
|
|
67
|
+
strel_tail=cv2.getStructuringElement(
|
|
68
|
+
cv2.MORPH_ELLIPSE, clean_parameters.strel_tail
|
|
69
|
+
),
|
|
70
|
+
iters_tail=clean_parameters.iters_tail,
|
|
71
|
+
strel_min=cv2.getStructuringElement(cv2.MORPH_RECT, clean_parameters.strel_min),
|
|
72
|
+
iters_min=clean_parameters.iters_min,
|
|
73
|
+
)
|
|
74
|
+
frames = np.concatenate((frames, shifted_frames, cleaned_frames), axis=0)
|
|
75
|
+
print(
|
|
76
|
+
f"Training data shape: {frames.shape}; memory usage: {frames.nbytes / 1e9 * 4:0.2f} GB"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
flipped = np.concatenate((flipped, flipped, flipped), axis=0)
|
|
80
|
+
|
|
81
|
+
np.savez(out_path, frames=frames, flipped=flipped)
|
|
82
|
+
return out_path
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def flatten(array: np.ndarray) -> np.ndarray:
|
|
86
|
+
return array.reshape(len(array), -1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def batch_apply_pca(frames: np.ndarray, pca: PCA, batch_size: int = 1000) -> np.ndarray:
|
|
90
|
+
output = []
|
|
91
|
+
if len(frames) < batch_size:
|
|
92
|
+
return pca.transform(flatten(frames)).astype(np.float32)
|
|
93
|
+
|
|
94
|
+
for arr in np.array_split(frames, len(frames) // batch_size):
|
|
95
|
+
output.append(pca.transform(flatten(arr)).astype(np.float32))
|
|
96
|
+
return np.concatenate(output, axis=0).astype(np.float32)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def train_classifier(
|
|
100
|
+
data_path: str,
|
|
101
|
+
classifier: str = "SVM",
|
|
102
|
+
n_components: int = 20,
|
|
103
|
+
):
|
|
104
|
+
"""Train a classifier to predict the orientation of a mouse.
|
|
105
|
+
Parameters:
|
|
106
|
+
data_path (str): Path to the training data numpy file.
|
|
107
|
+
classifier (str): Classifier to use. Either 'SVM' or 'RF'.
|
|
108
|
+
n_components (int): Number of components to keep in PCA."""
|
|
109
|
+
data = np.load(data_path)
|
|
110
|
+
frames = data["frames"]
|
|
111
|
+
flipped = data["flipped"]
|
|
112
|
+
|
|
113
|
+
print("Fitting PCA")
|
|
114
|
+
pca = PCA(n_components=n_components)
|
|
115
|
+
pca.fit(flatten(frames[-len(frames) // 3:]))
|
|
116
|
+
|
|
117
|
+
pipeline = make_pipeline(
|
|
118
|
+
FunctionTransformer(batch_apply_pca, kw_args={"pca": pca}, validate=False),
|
|
119
|
+
StandardScaler(),
|
|
120
|
+
(
|
|
121
|
+
RandomForestClassifier(n_estimators=150)
|
|
122
|
+
if classifier == "RF"
|
|
123
|
+
else SVC(probability=True)
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
print("Running cross-validation")
|
|
128
|
+
accuracy = cross_val_score(
|
|
129
|
+
pipeline, frames, flipped, cv=KFold(n_splits=4, shuffle=True, random_state=0)
|
|
130
|
+
)
|
|
131
|
+
print(f"Held-out model accuracy: {accuracy.mean()}")
|
|
132
|
+
|
|
133
|
+
print("Final fitting step")
|
|
134
|
+
return pipeline.fit(frames, flipped)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def save_classifier(clf_pipeline, out_path: str):
|
|
138
|
+
joblib.dump(clf_pipeline, out_path)
|
|
139
|
+
print(f"Classifier saved to {out_path}")
|