moseq2-model 1.4.0__py3-none-any.whl

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.
@@ -0,0 +1 @@
1
+ __version__ = "1.4.0"
moseq2_model/_yaml.py ADDED
@@ -0,0 +1,46 @@
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 files contain both
10
+ shapes -- ``wall_time: 06:00:00`` is interpolated straight into an ``sbatch -t``
11
+ argument by ``util.get_parameter_strings`` -- so switching parsers would quietly
12
+ change how existing user configs are read.
13
+ """
14
+
15
+ import io
16
+
17
+ from ruamel.yaml import YAML
18
+
19
+ # ``pure=True`` selects the Python implementation, matching what the module-level
20
+ # ``safe_load`` used and keeping behaviour identical whether or not the C
21
+ # extension is available.
22
+ _yaml = YAML(typ="safe", pure=True)
23
+
24
+
25
+ def read_yaml(path_or_stream):
26
+ """Parse a YAML file, given a path or an open stream."""
27
+ if hasattr(path_or_stream, "read"):
28
+ return _yaml.load(path_or_stream)
29
+ with open(path_or_stream, "r") as handle:
30
+ return _yaml.load(handle)
31
+
32
+
33
+ def write_yaml(data, path_or_stream):
34
+ """Write ``data`` as YAML, given a path or an open stream."""
35
+ if hasattr(path_or_stream, "write"):
36
+ _yaml.dump(data, path_or_stream)
37
+ return
38
+ with open(path_or_stream, "w") as handle:
39
+ _yaml.dump(data, handle)
40
+
41
+
42
+ def dump_yaml(data):
43
+ """Serialize ``data`` to a YAML string."""
44
+ stream = io.StringIO()
45
+ _yaml.dump(data, stream)
46
+ return stream.getvalue()
moseq2_model/cli.py ADDED
@@ -0,0 +1,326 @@
1
+ """
2
+ CLI for modeling the data using AR-HMM.
3
+ """
4
+
5
+ import os
6
+ import click
7
+ from os.path import join
8
+ from moseq2_model.util import count_frames as count_frames_wrapper
9
+ from moseq2_model.helpers.wrappers import (
10
+ learn_model_wrapper,
11
+ kappa_scan_fit_models_wrapper,
12
+ apply_model_wrapper,
13
+ )
14
+
15
+ orig_init = click.core.Option.__init__
16
+
17
+
18
+ def new_init(self, *args, **kwargs):
19
+ orig_init(self, *args, **kwargs)
20
+ self.show_default = True
21
+
22
+
23
+ click.core.Option.__init__ = new_init
24
+
25
+
26
+ @click.group()
27
+ @click.version_option()
28
+ def cli():
29
+ pass
30
+
31
+
32
+ @cli.command(
33
+ name="count-frames", help="Count the number of frames in given h5 file (pca_scores)"
34
+ )
35
+ @click.argument("input_file", type=click.Path(exists=True))
36
+ @click.option(
37
+ "--var-name",
38
+ type=str,
39
+ default="scores",
40
+ help="Variable name in input file with PCs",
41
+ )
42
+ def count_frames(input_file, var_name):
43
+ # Count the number of frames in the input file.
44
+ count_frames_wrapper(input_file=input_file, var_name=var_name)
45
+
46
+
47
+ def modeling_parameters(function):
48
+
49
+ function = click.option(
50
+ "--hold-out",
51
+ "-h",
52
+ is_flag=True,
53
+ default=False,
54
+ help="Hold out one fold (set by nfolds) for computing heldout likelihood",
55
+ )(function)
56
+ function = click.option(
57
+ "--hold-out-seed",
58
+ type=int,
59
+ default=-1,
60
+ help="Random seed for holding out data (set for reproducibility)",
61
+ )(function)
62
+ function = click.option(
63
+ "--nfolds", type=int, default=5, help="Number of folds for split"
64
+ )(function)
65
+ function = click.option(
66
+ "--ncpus",
67
+ "-c",
68
+ type=int,
69
+ default=0,
70
+ help="Number of cores to use for resampling",
71
+ )(function)
72
+ function = click.option(
73
+ "--num-iter",
74
+ "-n",
75
+ type=int,
76
+ default=100,
77
+ help="Number of interations to resample model",
78
+ )(function)
79
+ function = click.option(
80
+ "--var-name",
81
+ type=str,
82
+ default="scores",
83
+ help="Variable name in input file with PCs",
84
+ )(function)
85
+ function = click.option(
86
+ "--e-step",
87
+ is_flag=True,
88
+ default=False,
89
+ help="Compute the expected state sequence for each recordings",
90
+ )(function)
91
+ function = click.option(
92
+ "--save-every",
93
+ "-s",
94
+ type=int,
95
+ default=-1,
96
+ help="Increment to save labels and model object (-1 for just last)",
97
+ )(function)
98
+ function = click.option(
99
+ "--save-model",
100
+ type=bool,
101
+ default=True,
102
+ help="Save model object at the end of training",
103
+ )(function)
104
+ function = click.option(
105
+ "--max-states", "-m", type=int, default=100, help="Maximum number of states"
106
+ )(function)
107
+ function = click.option(
108
+ "--npcs", type=int, default=10, help="Number of PCs to use"
109
+ )(function)
110
+ function = click.option(
111
+ "--whiten",
112
+ "-w",
113
+ type=str,
114
+ default="all",
115
+ help="Whiten PCs: (e)each session (a)ll combined or (n)o whitening",
116
+ )(function)
117
+ function = click.option(
118
+ "--progressbar", "-p", type=bool, default=True, help="Show model progress"
119
+ )(function)
120
+ function = click.option(
121
+ "--percent-split",
122
+ type=int,
123
+ default=0,
124
+ help="Training-validation split percentage used when not holding out data and when this parameter > 0.",
125
+ )(function)
126
+ function = click.option(
127
+ "--load-groups",
128
+ type=bool,
129
+ default=True,
130
+ help="If groups should be loaded with the PC scores.",
131
+ )(function)
132
+ function = click.option(
133
+ "--gamma",
134
+ "-g",
135
+ type=float,
136
+ default=1e3,
137
+ help="Gamma; hierarchical dirichlet process hyperparameter (try not to change it).",
138
+ )(function)
139
+ function = click.option(
140
+ "--alpha",
141
+ "-a",
142
+ type=float,
143
+ default=5.7,
144
+ help="Alpha; hierarchical dirichlet process hyperparameter (try not to change it).",
145
+ )(function)
146
+ function = click.option(
147
+ "--noise-level",
148
+ type=float,
149
+ default=0,
150
+ help="Additive white gaussian noise to input data for regularization. Not generally used",
151
+ )(function)
152
+ function = click.option(
153
+ "--nlags", type=int, default=3, help="Number of lags to use"
154
+ )(function)
155
+ function = click.option(
156
+ "--separate-trans",
157
+ is_flag=True,
158
+ default=False,
159
+ help="Use separate transition matrix for each group",
160
+ )(function)
161
+ function = click.option(
162
+ "--robust",
163
+ is_flag=True,
164
+ default=False,
165
+ help="Use robust AR-HMM model. More tolerant to noise",
166
+ )(function)
167
+ function = click.option(
168
+ "--check-every",
169
+ type=int,
170
+ default=5,
171
+ help="Increment to record training and validation log-likelihoods.",
172
+ )(function)
173
+
174
+ return function
175
+
176
+
177
+ @cli.command(
178
+ name="learn-model", help="Train ARHMM on PC Scores with given training parameters"
179
+ )
180
+ @click.argument("input_file", type=click.Path(exists=True))
181
+ @click.argument(
182
+ "dest_file", type=click.Path(file_okay=True, writable=True, resolve_path=True)
183
+ )
184
+ @modeling_parameters
185
+ @click.option(
186
+ "--kappa",
187
+ "-k",
188
+ type=float,
189
+ default=None,
190
+ help="Kappa; hyperparameter used to set syllable duration. Larger k = longer syllable lengths",
191
+ )
192
+ @click.option(
193
+ "--checkpoint-freq",
194
+ type=int,
195
+ default=-1,
196
+ help="save model checkpoint every n iterations",
197
+ )
198
+ @click.option(
199
+ "--use-checkpoint",
200
+ is_flag=True,
201
+ default=False,
202
+ help="indicate whether to use previously saved checkpoint",
203
+ )
204
+ @click.option(
205
+ "--index",
206
+ "-i",
207
+ type=click.Path(),
208
+ default="",
209
+ help="Path to moseq2-index.yaml for group definitions",
210
+ )
211
+ @click.option(
212
+ "--default-group",
213
+ type=str,
214
+ default="n/a",
215
+ help="Default group name to use for separate-trans",
216
+ )
217
+ @click.option(
218
+ "--verbose",
219
+ "-v",
220
+ is_flag=True,
221
+ default=False,
222
+ help="Print syllable log-likelihoods during training.",
223
+ )
224
+ def learn_model(input_file, dest_file, **config_data):
225
+ # Train the ARHMM using PC scores located in the INPUT_FILE, and saves the model to DEST_FILE
226
+
227
+ learn_model_wrapper(input_file, dest_file, config_data)
228
+
229
+
230
+ @cli.command(name="apply-model", help="Apply pre-trained ARHMM to PC scores.")
231
+ @click.argument("model_file", type=click.Path(exists=True))
232
+ @click.argument("pc_file", type=click.Path(exists=True))
233
+ @click.argument(
234
+ "dest_file", type=click.Path(file_okay=True, writable=True, resolve_path=True)
235
+ )
236
+ @click.option(
237
+ "--var-name",
238
+ type=str,
239
+ default="scores",
240
+ help="Variable name in input file with PCs",
241
+ )
242
+ @click.option(
243
+ "--index",
244
+ "-i",
245
+ type=click.Path(),
246
+ default="",
247
+ help="Path to moseq2-index.yaml for group definitions",
248
+ )
249
+ @click.option(
250
+ "--load-groups",
251
+ type=bool,
252
+ default=True,
253
+ help="If groups should be loaded with the PC scores.",
254
+ )
255
+ def apply_model(model_file, pc_file, dest_file, **config_data):
256
+ # Apply the ARHMM located in MODEL_FILE to the PC scores in PC_FILE, and saves the results to DEST_FILE
257
+
258
+ apply_model_wrapper(model_file, pc_file, dest_file, config_data)
259
+
260
+
261
+ @cli.command(
262
+ name="kappa-scan",
263
+ help="Batch train multiple model to scan over different kappa values.",
264
+ )
265
+ @click.argument("input_file", type=click.Path(exists=True))
266
+ @click.argument("output_dir", type=click.Path(exists=False))
267
+ @click.option(
268
+ "--index",
269
+ "-i",
270
+ type=click.Path(),
271
+ default="",
272
+ help="Path to moseq2-index.yaml for session metadata and group information",
273
+ )
274
+ @click.option(
275
+ "--out-script",
276
+ type=click.Path(),
277
+ default="train_out.sh",
278
+ help="Name of bash script file to save model training commands.",
279
+ )
280
+ @click.option(
281
+ "--n-models", type=int, default=10, help="Number of models to train in kappa scan."
282
+ )
283
+ @click.option(
284
+ "--prefix",
285
+ type=str,
286
+ default="",
287
+ help="Batch command string to prefix model training command (slurm only).",
288
+ )
289
+ @click.option(
290
+ "--cluster-type",
291
+ type=click.Choice(["local", "slurm"]),
292
+ default="local",
293
+ help="Platform to train models on",
294
+ )
295
+ @click.option(
296
+ "--scan-scale",
297
+ type=click.Choice(["log", "linear"]),
298
+ default="log",
299
+ help="Scale to scan kappa values at.",
300
+ )
301
+ @click.option(
302
+ "--min-kappa",
303
+ type=float,
304
+ default=None,
305
+ help="Minimum kappa value to begin scan from.",
306
+ )
307
+ @click.option(
308
+ "--max-kappa", type=float, default=None, help="Maximum kappa value to end scan on."
309
+ )
310
+ @click.option("--memory", type=str, default="5GB", help="RAM (slurm only)")
311
+ @click.option("--wall-time", type=str, default="3:00:00", help="Wall time (slurm only)")
312
+ @click.option(
313
+ "--partition", type=str, default="short", help="Partition name (slurm only)"
314
+ )
315
+ @click.option("--get-cmd", is_flag=True, default=False, help="Print scan command strings.")
316
+ @click.option("--run-cmd", is_flag=True, default=False, help="Run scan command strings.")
317
+ @modeling_parameters
318
+ def kappa_scan_fit_models(input_file, output_dir, **config_data):
319
+ # Scan through the kappa hyperparameter to find the kappa that best matches the changepoint duration distribution.
320
+
321
+ config_data["out_script"] = join(output_dir, config_data["out_script"])
322
+ kappa_scan_fit_models_wrapper(input_file, config_data, output_dir)
323
+
324
+
325
+ if __name__ == "__main__":
326
+ cli()
moseq2_model/gui.py ADDED
@@ -0,0 +1,104 @@
1
+ """
2
+ GUI front-end functions for training ARHMM.
3
+ """
4
+
5
+ from moseq2_model.cli import learn_model, kappa_scan_fit_models
6
+ from os.path import dirname, join, exists
7
+ from moseq2_model.helpers.wrappers import (
8
+ learn_model_wrapper,
9
+ kappa_scan_fit_models_wrapper,
10
+ apply_model_wrapper,
11
+ )
12
+ from moseq2_model._yaml import read_yaml
13
+
14
+
15
+ def learn_model_command(progress_paths, get_cmd=True, verbose=False):
16
+ """
17
+ Train ARHMM from within a Jupyter notebook using parameters specified in the notebook.
18
+
19
+ Args:
20
+ progress_paths (dict): notebook progress dict that contains paths to the pc scores, config, and index files.
21
+ get_cmd (bool): flag to return the kappa scan learn-model commands.
22
+ verbose (bool): compute modeling summary - can slow down training.
23
+
24
+ Returns:
25
+ None or kappa scan command
26
+ """
27
+
28
+ # Load proper input variables
29
+ input_file = progress_paths["scores_path"]
30
+ dest_file = progress_paths["model_path"]
31
+ config_file = progress_paths["config_file"]
32
+ index = progress_paths["index_file"]
33
+ output_dir = progress_paths["base_model_path"]
34
+
35
+ assert exists(
36
+ input_file
37
+ ), "PCA Score not found; set the correct path in progress_paths['scores_path']"
38
+ assert exists(
39
+ config_file
40
+ ), "Config file not found; set the correct path in progress_paths['config_file']"
41
+ assert exists(
42
+ index
43
+ ), "Index file not found; set the correct path in progress_paths['index_file']"
44
+
45
+ with open(config_file, "r") as f:
46
+ config_data = read_yaml(f)
47
+
48
+ # Get default CLI params
49
+ params = {tmp.name: tmp.default for tmp in learn_model.params if not tmp.required}
50
+ # merge default params and config data, preferring values in config data
51
+ config_data = {**params, **config_data}
52
+
53
+ config_data["verbose"] = verbose
54
+ config_data["index"] = index
55
+
56
+ if output_dir is None:
57
+ print("Output directory not specified, saving models to base directory.")
58
+ output_dir = dirname(index)
59
+
60
+ if config_data["kappa"] == "scan":
61
+ assert config_data["scan_scale"] in (
62
+ "log",
63
+ "linear",
64
+ ), 'scan scale must be "log" or "linear"'
65
+
66
+ # Get default CLI params
67
+ params = {
68
+ tmp.name: tmp.default
69
+ for tmp in kappa_scan_fit_models.params
70
+ if not tmp.required
71
+ }
72
+ # merge default params and config data, preferring values in config data
73
+ config_data = {**params, **config_data}
74
+
75
+ config_data["out_script"] = join(output_dir, config_data["out_script"])
76
+ config_data["get_cmd"] = get_cmd
77
+
78
+ command = kappa_scan_fit_models_wrapper(input_file, config_data, output_dir)
79
+ return command
80
+ else:
81
+ learn_model_wrapper(input_file, dest_file, config_data)
82
+
83
+
84
+ def apply_model_command(progress_paths, model_file):
85
+ """Apply a pre-trained ARHMM to a new dataset from within a Jupyter notebook.
86
+
87
+ Args:
88
+ progress_paths (dict): notebook progress dict that contains paths to the pc scores, config, and index files.
89
+ model_file (str): path to the pre-trained ARHMM.
90
+ """
91
+
92
+ # Load proper input variables
93
+ pc_file = progress_paths["scores_path"]
94
+ dest_file = progress_paths["model_path"]
95
+ config_file = progress_paths["config_file"]
96
+ index = progress_paths["index_file"]
97
+ output_dir = progress_paths["base_model_path"]
98
+
99
+ # load config data
100
+ with open(config_file, "r") as f:
101
+ config_data = read_yaml(f)
102
+
103
+ # apply model to data
104
+ apply_model_wrapper(model_file, pc_file, dest_file, config_data)
File without changes