wristpy.core.orchestrator

Python based runner.

  1"""Python based runner."""
  2
  3import itertools
  4import logging
  5import pathlib
  6from typing import Dict, Literal, Optional, Sequence, Tuple, Union
  7
  8from wristpy.core import config, exceptions, models
  9from wristpy.io.readers import readers
 10from wristpy.io.writers import writers
 11from wristpy.processing import (
 12    analytics,
 13    calibration,
 14    idle_sleep_mode_imputation,
 15    metrics,
 16    processing_utils,
 17)
 18
 19logger = config.get_logger()
 20
 21VALID_FILE_TYPES = (".csv", ".parquet")
 22
 23DEFAULT_ACTIVITY_THRESHOLDS = {
 24    "enmo": (0.0563, 0.1916, 0.6958),
 25    "mad": (0.029, 0.338, 0.604),
 26    "ag_count": (100, 3000, 5200),
 27    "mims": (10.558, 15.047, 19.614),
 28}
 29
 30
 31def run(
 32    input: Union[pathlib.Path, str],
 33    output: Optional[Union[pathlib.Path, str]] = None,
 34    thresholds: Optional[Sequence[Tuple[float, float, float]]] = None,
 35    calibrator: Union[
 36        None,
 37        Literal["ggir", "gradient"],
 38    ] = "gradient",
 39    epoch_length: float = 5,
 40    activity_metric: Sequence[Literal["enmo", "mad", "ag_count", "mims"]] = ["enmo"],
 41    nonwear_algorithm: Sequence[Literal["ggir", "cta", "detach"]] = ["ggir"],
 42    verbosity: int = logging.WARNING,
 43    output_filetype: Literal[".csv", ".parquet"] = ".csv",
 44) -> Union[writers.OrchestratorResults, Dict[str, writers.OrchestratorResults]]:
 45    """Runs main processing steps for wristpy on single files, or directories.
 46
 47    The run() function will execute the run_file() function on individual files, or
 48    run_directory() on entire directories. When the input path points to a file, the
 49    name of the save file will be taken from the given output path (if any). When the
 50    input path points to a directory the output path must be a valid directory as well.
 51    Output file names will be derived from original file names in the case of directory
 52    processing.
 53
 54    Args:
 55        input: Path to the input file or directory of files to be read. Currently,
 56            this supports .bin and .gt3x
 57        output: Path to directory data will be saved to. If processing a single file the
 58            path should end in the save file name in either .csv or .parquet formats.
 59        thresholds: The cut points for the light, moderate, and vigorous thresholds,
 60            given in that order. One threshold tuple must be provided for each activity
 61            metric, in the same order the metrics were specified. To use default values
 62            for all metrics, leave thresholds as None. Values must be ascending, unique,
 63            and greater than 0. Default values are optimized for subjects ages 7-11 [1]
 64            [3].
 65        calibrator: The calibrator to be used on the input data.
 66        epoch_length: The temporal resolution in seconds, the data will be down sampled
 67            to. It must be > 0.0.
 68        activity_metric: The metric(s) to be used for physical activity categorization.
 69            Multiple metrics can be specified as a sequence.
 70        nonwear_algorithm: The algorithm to be used for nonwear detection.
 71        verbosity: The logging level for the logger.
 72        output_filetype: Specifies the data format for the save files. Only used when
 73            processing directories.
 74
 75    Returns:
 76        All calculated data in a save ready format as a Results object or as a
 77        dictionary of OrchestratorResults objects.
 78
 79    Raises:
 80        ValueError: If the number of physical activity thresholds does not match the
 81            number of provided activity metrics.
 82        ValueError: If the physical activity thresholds are not unique or not in
 83            ascending order.
 84
 85    References:
 86        [1] Hildebrand, M., et al. (2014). Age group comparability of raw accelerometer
 87        output from wrist- and hip-worn monitors. Medicine and Science in Sports and
 88        Exercise, 46(9), 1816-1824.
 89        [2] Treuth MS, Schmitz K, Catellier DJ, McMurray RG, Murray DM, Almeida MJ,
 90        Going S, Norman JE, Pate R. Defining accelerometer thresholds for activity
 91        intensities in adolescent girls. Med Sci Sports Exerc. 2004 Jul;36(7):1259-66.
 92        PMID: 15235335; PMCID: PMC2423321.
 93        [3] Karas M, Muschelli J, Leroux A, Urbanek J, Wanigatunga A, Bai J,
 94        Crainiceanu C, Schrack J Comparison of Accelerometry-Based Measures of Physical
 95        Activity: Retrospective Observational Data Analysis Study JMIR Mhealth Uhealth
 96        2022;10(7):e38077 URL: https://mhealth.jmir.org/2022/7/e38077 DOI: 10.2196/38077
 97    """
 98    logger.setLevel(verbosity)
 99
100    input = pathlib.Path(input)
101    output = pathlib.Path(output) if output is not None else None
102
103    if thresholds is not None:
104        if len(activity_metric) != len(thresholds):
105            raise ValueError(
106                "Number of thresholds did not match the number of activity metrics. "
107                "Provide one threshold tuple per metric or use None for defaults."
108            )
109        metrics_dict = dict(zip(activity_metric, thresholds))
110    else:
111        metrics_dict = {
112            metric: DEFAULT_ACTIVITY_THRESHOLDS[metric] for metric in activity_metric
113        }
114
115    for metric, thresh in metrics_dict.items():
116        if not (0 <= thresh[0] < thresh[1] < thresh[2]):
117            message = (
118                f"Invalid thresholds for {metric}."
119                f" Threshold values must be >=0, unique, and in ascending order."
120            )
121            logger.error(message)
122            raise ValueError(message)
123
124    if input.is_file():
125        return _run_file(
126            input=input,
127            output=output,
128            thresholds=thresholds,
129            calibrator=calibrator,
130            epoch_length=epoch_length,
131            activity_metric=activity_metric,
132            verbosity=verbosity,
133            nonwear_algorithm=nonwear_algorithm,
134        )
135
136    return _run_directory(
137        input=input,
138        output=output,
139        thresholds=thresholds,
140        calibrator=calibrator,
141        epoch_length=epoch_length,
142        activity_metric=activity_metric,
143        verbosity=verbosity,
144        output_filetype=output_filetype,
145        nonwear_algorithm=nonwear_algorithm,
146    )
147
148
149def _run_directory(
150    input: pathlib.Path,
151    output: Optional[pathlib.Path] = None,
152    thresholds: Optional[Sequence[Tuple[float, float, float]]] = None,
153    calibrator: Union[
154        None,
155        Literal["ggir", "gradient"],
156    ] = "gradient",
157    epoch_length: float = 5,
158    nonwear_algorithm: Sequence[Literal["ggir", "cta", "detach"]] = ["ggir"],
159    verbosity: int = logging.WARNING,
160    output_filetype: Literal[".csv", ".parquet"] = ".csv",
161    activity_metric: Sequence[Literal["enmo", "mad", "ag_count", "mims"]] = ["enmo"],
162) -> Dict[str, writers.OrchestratorResults]:
163    """Runs main processing steps for wristpy on directories.
164
165    The _run_directory() function will execute the _run_file() function on entire
166    directories. The input and output (if any) paths must be directories. Output file
167    names will be derived from input file names.
168
169    Args:
170        input: Path to the input directory of files to be read. Currently,
171            this supports .bin and .gt3x
172        output: Path to directory data will be saved to.
173        thresholds: The cut points for the light, moderate, and vigorous thresholds,
174            given in that order. One threshold tuple must be provided for each activity
175            metric, in the same order the metrics were specified. To use default values
176            for all metrics, leave thresholds as None. Values must be ascending, unique,
177            and greater than 0. Default values are optimized for subjects ages 7-11
178            [1][2].
179        calibrator: The calibrator to be used on the input data.
180        epoch_length: The temporal resolution in seconds, the data will be down sampled
181            to. It must be > 0.0.
182        nonwear_algorithm: The algorithm to be used for nonwear detection.
183        verbosity: The logging level for the logger.
184        output_filetype: Specifies the data format for the save files.
185        activity_metric: The metric(s) to be used for physical activity categorization.
186            Multiple metrics can be specified as a sequence.
187
188    Returns:
189        All calculated data in a save ready format as a dictionary of
190        OrchestratorResults objects.
191
192    Raises:
193        ValueError: If the output given is not a directory.
194        ValueError: If the output_filetype is not a valid type.
195        FileNotFoundError: If the input directory contained no files of a valid type.
196
197    References:
198        [1] Hildebrand, M., et al. (2014). Age group comparability of raw accelerometer
199        output from wrist- and hip-worn monitors. Medicine and Science in Sports and
200        Exercise, 46(9), 1816-1824.
201        [2] Karas M, Muschelli J, Leroux A, Urbanek J, Wanigatunga A, Bai J,
202        Crainiceanu C, Schrack J Comparison of Accelerometry-Based Measures of Physical
203        Activity: Retrospective Observational Data Analysis Study JMIR Mhealth Uhealth
204        2022;10(7):e38077 URL: https://mhealth.jmir.org/2022/7/e38077 DOI: 10.2196/38077
205    """
206    if output is not None:
207        if output.is_file():
208            raise ValueError(
209                "Output is a file, but must be a directory when input is a directory."
210            )
211        if output_filetype not in VALID_FILE_TYPES:
212            raise ValueError(
213                "Invalid output_filetype: "
214                f"{output_filetype}. Valid options are: {VALID_FILE_TYPES}."
215            )
216
217    file_names = list(itertools.chain(input.glob("*.gt3x"), input.glob("*.bin")))
218
219    if not file_names:
220        raise exceptions.EmptyDirectoryError(
221            f"Directory {input} contains no .gt3x or .bin files."
222        )
223    results_dict = {}
224    for file in file_names:
225        output_file_path = (
226            output / pathlib.Path(file.stem).with_suffix(output_filetype)
227            if output
228            else None
229        )
230        logger.debug(
231            "Processing directory: %s, current file: %s, save path: %s",
232            input,
233            file,
234            output_file_path,
235        )
236        try:
237            results_dict[str(file)] = _run_file(
238                input=input / file,
239                output=output_file_path,
240                thresholds=thresholds,
241                calibrator=calibrator,
242                epoch_length=epoch_length,
243                verbosity=verbosity,
244                nonwear_algorithm=nonwear_algorithm,
245                activity_metric=activity_metric,
246            )
247        except Exception as e:
248            logger.error("Did not run file: %s, Error: %s", file, e)
249    logger.info("Processing for directory %s completed successfully.", output)
250    return results_dict
251
252
253def _run_file(
254    input: pathlib.Path,
255    output: Optional[pathlib.Path] = None,
256    thresholds: Optional[Sequence[Tuple[float, float, float]]] = None,
257    calibrator: Union[
258        None,
259        Literal["ggir", "gradient"],
260    ] = "gradient",
261    epoch_length: float = 5.0,
262    activity_metric: Sequence[Literal["enmo", "mad", "ag_count", "mims"]] = ["enmo"],
263    nonwear_algorithm: Sequence[Literal["ggir", "cta", "detach"]] = ["ggir"],
264    verbosity: int = logging.WARNING,
265) -> writers.OrchestratorResults:
266    """Runs main processing steps for wristpy and returns data for analysis.
267
268    The run_file() function will provide the user with the specified physical activity
269    metric(s), anglez, physical activity levels, detected sleep periods, and nonwear
270    data. All measures will be in the same temporal resolution. Users may choose from
271    'ggir' and 'gradient' calibration methods, or enter None to proceed without
272    calibration.
273
274    Args:
275        input: Path to the input file to be read. Currently, this supports .bin and
276            .gt3x
277        output: Path to save data to. The path should end in the save file name in
278            either .csv or .parquet formats.
279        thresholds: The cut points for the light, moderate, and vigorous thresholds,
280            given in that order. One threshold tuple must be provided for each activity
281            metric, in the same order the metrics were specified. To use default values
282            for all metrics, leave thresholds as None. Values must be ascending, unique,
283            and greater than 0. Default values are optimized for subjects ages 7-11
284            [1]-[3].
285        calibrator: The calibrator to be used on the input data.
286        epoch_length: The temporal resolution in seconds, the data will be down sampled
287            to. It must be > 0.0.
288        activity_metric: The metric(s) to be used for physical activity categorization.
289            Multiple metrics can be specified as a sequence.
290        nonwear_algorithm: The algorithm to be used for nonwear detection. A sequence of
291            algorithms can be provided. If so, a majority vote will be taken.
292        verbosity: The logging level for the logger.
293
294    Returns:
295        All calculated data in a save ready format as a OrchestratorResults object.
296
297    Raises:
298        ValueError: If the number of physical activity thresholds does not match the
299            number of provided activity metrics.
300        ValueError: If the physical activity thresholds are not unique or not in
301            ascending order.
302        ValueError: If an invalid Calibrator is chosen.
303        ValueError: If epoch_length is not greater than 0.
304
305    References:
306        [1] Hildebrand, M., et al. (2014). Age group comparability of raw accelerometer
307        output from wrist- and hip-worn monitors. Medicine and Science in Sports and
308        Exercise, 46(9), 1816-1824.
309        [2] Aittasalo, M., Vähä-Ypyä, H., Vasankari, T. et al. Mean amplitude deviation
310        calculated from raw acceleration data: a novel method for classifying the
311        intensity of adolescents' physical activity irrespective of accelerometer brand.
312        BMC Sports Sci Med Rehabil 7, 18 (2015). https://doi.org/10.1186/s13102-015-0010-0.
313        [3] Karas M, Muschelli J, Leroux A, Urbanek J, Wanigatunga A, Bai J,
314        Crainiceanu C, Schrack J Comparison of Accelerometry-Based Measures of Physical
315        Activity: Retrospective Observational Data Analysis Study JMIR Mhealth Uhealth
316        2022;10(7):e38077 URL: https://mhealth.jmir.org/2022/7/e38077 DOI: 10.2196/38077
317    """
318    logger.setLevel(verbosity)
319    if output is not None:
320        writers.OrchestratorResults.validate_output(output=output)
321
322    if calibrator is not None and calibrator not in ["ggir", "gradient"]:
323        msg = (
324            f"Invalid calibrator: {calibrator}. Choose: 'ggir', 'gradient'. "
325            "Enter None if no calibration is desired."
326        )
327        logger.error(msg)
328        raise ValueError(msg)
329
330    if epoch_length <= 0:
331        msg = "Epoch_length must be greater than 0."
332        logger.error(msg)
333        raise ValueError(msg)
334
335    watch_data = readers.read_watch_data(input)
336
337    if calibrator is None:
338        logger.debug("Running without calibration")
339        calibrated_acceleration = watch_data.acceleration
340    else:
341        calibrated_acceleration = calibration.CalibrationDispatcher(calibrator).run(
342            watch_data.acceleration, return_input_on_error=True
343        )
344
345    if watch_data.idle_sleep_mode_flag:
346        logger.debug("Imputing idle sleep mode gaps.")
347        calibrated_acceleration = (
348            idle_sleep_mode_imputation.impute_idle_sleep_mode_gaps(
349                calibrated_acceleration
350            )
351        )
352
353    anglez = metrics.angle_relative_to_horizontal(
354        calibrated_acceleration, epoch_length=epoch_length
355    )
356
357    metrics_dict: Dict[
358        Literal["enmo", "mad", "ag_count", "mims"], Tuple[float, float, float]
359    ]
360    if thresholds is not None:
361        metrics_dict = dict(zip(activity_metric, thresholds))
362    else:
363        metrics_dict = {
364            metric: DEFAULT_ACTIVITY_THRESHOLDS[metric] for metric in activity_metric
365        }
366
367    activity_measurements_list = []
368    physical_activity_levels_list = []
369    for metric_name, thresh in metrics_dict.items():
370        metric_measurement = _compute_activity(
371            calibrated_acceleration,
372            metric_name,
373            epoch_length,
374            dynamic_range=watch_data.dynamic_range,
375        )
376        activity_measurements_list.append(metric_measurement)
377        physical_activity_levels_list.append(
378            analytics.compute_physical_activty_categories(
379                metric_measurement,
380                thresh,
381                name=f"{metric_name} physical activity levels",
382            )
383        )
384
385    nonwear_array = processing_utils.get_nonwear_measurements(
386        calibrated_acceleration=calibrated_acceleration,
387        temperature=watch_data.temperature,
388        non_wear_algorithms=nonwear_algorithm,
389    )
390
391    nonwear_epoch = processing_utils.synchronize_measurements(
392        data_measurement=nonwear_array,
393        reference_measurement=anglez,
394        epoch_length=epoch_length,
395    )
396
397    sleep_detector = analytics.GgirSleepDetection(anglez)
398    sleep_parameters = sleep_detector.run_sleep_detection()
399    sleep_array = analytics.sleep_cleanup(
400        sleep_windows=sleep_parameters.sleep_windows, nonwear_measurement=nonwear_epoch
401    )
402    spt_windows = analytics.sleep_bouts_cleanup(
403        sleep_parameter=sleep_parameters.spt_windows,
404        nonwear_measurement=nonwear_epoch,
405        time_reference_measurement=anglez,
406        epoch_length=epoch_length,
407    )
408    sib_periods = analytics.sleep_bouts_cleanup(
409        sleep_parameter=sleep_parameters.sib_periods,
410        nonwear_measurement=nonwear_epoch,
411        time_reference_measurement=anglez,
412        epoch_length=epoch_length,
413    )
414
415    parameters_dictionary = {
416        "thresholds": list(thresholds) if thresholds is not None else None,
417        "calibrator": calibrator,
418        "epoch_length": epoch_length,
419        "activity_metric": activity_metric,
420        "nonwear_algorithm": list(nonwear_algorithm),
421        "input_file": str(input),
422    }
423
424    results = writers.OrchestratorResults(
425        physical_activity_metric=activity_measurements_list,
426        anglez=anglez,
427        physical_activity_levels=physical_activity_levels_list,
428        sleep_status=sleep_array,
429        sib_periods=sib_periods,
430        spt_periods=spt_windows,
431        nonwear_status=nonwear_epoch,
432        processing_params=parameters_dictionary,
433    )
434    if output is not None:
435        try:
436            results.save_results(output=output)
437        except (
438            exceptions.InvalidFileTypeError,
439            PermissionError,
440            FileExistsError,
441        ) as exc_info:
442            # Allowed to pass to recover in Jupyter Notebook scenarios.
443            logger.error(
444                (
445                    "Could not save output due to: %s. Call save_results "
446                    "on the output object with a correct filename to save these "
447                    "results.",
448                    exc_info,
449                )
450            )
451    logger.info("Processing for %s completed successfully.", input.stem)
452    return results
453
454
455def _compute_activity(
456    acceleration: models.Measurement,
457    activity_metric: Literal["ag_count", "mad", "enmo", "mims"],
458    epoch_length: float,
459    dynamic_range: Optional[tuple[float, float]],
460) -> models.Measurement:
461    """This is a helper function to organize the computation of the activity metric.
462
463    This function organizes the logic for computing the requested physical activity
464    metric at the desired temporal resolution.
465
466    Args:
467        acceleration: The acceleration data to compute the activity metric from.
468        activity_metric: The metric to be used for physical activity categorization.
469        epoch_length: The temporal resolution in seconds, the data will be down sampled
470            to.
471        dynamic_range: Tuple of the minimum and maximum accelerometer values. This
472            argument is only relevant to the mims metric. Values are taken from watch
473            metadata, if no metadata could be extracted, the default
474            values of (-8,8) are used.
475
476    Returns:
477        A Measurement object with the computed physical activity metric.
478    """
479    if activity_metric == "ag_count":
480        return metrics.actigraph_activity_counts(
481            acceleration, epoch_length=epoch_length, name=activity_metric
482        )
483    elif activity_metric == "mad":
484        return metrics.mean_amplitude_deviation(
485            acceleration, epoch_length=epoch_length, name=activity_metric
486        )
487    elif activity_metric == "mims":
488        if dynamic_range is None:
489            return metrics.monitor_independent_movement_summary_units(
490                acceleration, epoch=epoch_length, name=activity_metric
491            )
492        return metrics.monitor_independent_movement_summary_units(
493            acceleration,
494            epoch=epoch_length,
495            dynamic_range=dynamic_range,
496            name=activity_metric,
497        )
498
499    return metrics.euclidean_norm_minus_one(
500        acceleration, epoch_length=epoch_length, name=activity_metric
501    )
logger = <Logger wristpy (INFO)>
VALID_FILE_TYPES = ('.csv', '.parquet')
DEFAULT_ACTIVITY_THRESHOLDS = {'enmo': (0.0563, 0.1916, 0.6958), 'mad': (0.029, 0.338, 0.604), 'ag_count': (100, 3000, 5200), 'mims': (10.558, 15.047, 19.614)}
def run( input: Union[pathlib.Path, str], output: Union[str, pathlib.Path, NoneType] = None, thresholds: Optional[Sequence[Tuple[float, float, float]]] = None, calibrator: Optional[Literal['ggir', 'gradient']] = 'gradient', epoch_length: float = 5, activity_metric: Sequence[Literal['enmo', 'mad', 'ag_count', 'mims']] = ['enmo'], nonwear_algorithm: Sequence[Literal['ggir', 'cta', 'detach']] = ['ggir'], verbosity: int = 30, output_filetype: Literal['.csv', '.parquet'] = '.csv') -> Union[wristpy.io.writers.writers.OrchestratorResults, Dict[str, wristpy.io.writers.writers.OrchestratorResults]]:
 32def run(
 33    input: Union[pathlib.Path, str],
 34    output: Optional[Union[pathlib.Path, str]] = None,
 35    thresholds: Optional[Sequence[Tuple[float, float, float]]] = None,
 36    calibrator: Union[
 37        None,
 38        Literal["ggir", "gradient"],
 39    ] = "gradient",
 40    epoch_length: float = 5,
 41    activity_metric: Sequence[Literal["enmo", "mad", "ag_count", "mims"]] = ["enmo"],
 42    nonwear_algorithm: Sequence[Literal["ggir", "cta", "detach"]] = ["ggir"],
 43    verbosity: int = logging.WARNING,
 44    output_filetype: Literal[".csv", ".parquet"] = ".csv",
 45) -> Union[writers.OrchestratorResults, Dict[str, writers.OrchestratorResults]]:
 46    """Runs main processing steps for wristpy on single files, or directories.
 47
 48    The run() function will execute the run_file() function on individual files, or
 49    run_directory() on entire directories. When the input path points to a file, the
 50    name of the save file will be taken from the given output path (if any). When the
 51    input path points to a directory the output path must be a valid directory as well.
 52    Output file names will be derived from original file names in the case of directory
 53    processing.
 54
 55    Args:
 56        input: Path to the input file or directory of files to be read. Currently,
 57            this supports .bin and .gt3x
 58        output: Path to directory data will be saved to. If processing a single file the
 59            path should end in the save file name in either .csv or .parquet formats.
 60        thresholds: The cut points for the light, moderate, and vigorous thresholds,
 61            given in that order. One threshold tuple must be provided for each activity
 62            metric, in the same order the metrics were specified. To use default values
 63            for all metrics, leave thresholds as None. Values must be ascending, unique,
 64            and greater than 0. Default values are optimized for subjects ages 7-11 [1]
 65            [3].
 66        calibrator: The calibrator to be used on the input data.
 67        epoch_length: The temporal resolution in seconds, the data will be down sampled
 68            to. It must be > 0.0.
 69        activity_metric: The metric(s) to be used for physical activity categorization.
 70            Multiple metrics can be specified as a sequence.
 71        nonwear_algorithm: The algorithm to be used for nonwear detection.
 72        verbosity: The logging level for the logger.
 73        output_filetype: Specifies the data format for the save files. Only used when
 74            processing directories.
 75
 76    Returns:
 77        All calculated data in a save ready format as a Results object or as a
 78        dictionary of OrchestratorResults objects.
 79
 80    Raises:
 81        ValueError: If the number of physical activity thresholds does not match the
 82            number of provided activity metrics.
 83        ValueError: If the physical activity thresholds are not unique or not in
 84            ascending order.
 85
 86    References:
 87        [1] Hildebrand, M., et al. (2014). Age group comparability of raw accelerometer
 88        output from wrist- and hip-worn monitors. Medicine and Science in Sports and
 89        Exercise, 46(9), 1816-1824.
 90        [2] Treuth MS, Schmitz K, Catellier DJ, McMurray RG, Murray DM, Almeida MJ,
 91        Going S, Norman JE, Pate R. Defining accelerometer thresholds for activity
 92        intensities in adolescent girls. Med Sci Sports Exerc. 2004 Jul;36(7):1259-66.
 93        PMID: 15235335; PMCID: PMC2423321.
 94        [3] Karas M, Muschelli J, Leroux A, Urbanek J, Wanigatunga A, Bai J,
 95        Crainiceanu C, Schrack J Comparison of Accelerometry-Based Measures of Physical
 96        Activity: Retrospective Observational Data Analysis Study JMIR Mhealth Uhealth
 97        2022;10(7):e38077 URL: https://mhealth.jmir.org/2022/7/e38077 DOI: 10.2196/38077
 98    """
 99    logger.setLevel(verbosity)
100
101    input = pathlib.Path(input)
102    output = pathlib.Path(output) if output is not None else None
103
104    if thresholds is not None:
105        if len(activity_metric) != len(thresholds):
106            raise ValueError(
107                "Number of thresholds did not match the number of activity metrics. "
108                "Provide one threshold tuple per metric or use None for defaults."
109            )
110        metrics_dict = dict(zip(activity_metric, thresholds))
111    else:
112        metrics_dict = {
113            metric: DEFAULT_ACTIVITY_THRESHOLDS[metric] for metric in activity_metric
114        }
115
116    for metric, thresh in metrics_dict.items():
117        if not (0 <= thresh[0] < thresh[1] < thresh[2]):
118            message = (
119                f"Invalid thresholds for {metric}."
120                f" Threshold values must be >=0, unique, and in ascending order."
121            )
122            logger.error(message)
123            raise ValueError(message)
124
125    if input.is_file():
126        return _run_file(
127            input=input,
128            output=output,
129            thresholds=thresholds,
130            calibrator=calibrator,
131            epoch_length=epoch_length,
132            activity_metric=activity_metric,
133            verbosity=verbosity,
134            nonwear_algorithm=nonwear_algorithm,
135        )
136
137    return _run_directory(
138        input=input,
139        output=output,
140        thresholds=thresholds,
141        calibrator=calibrator,
142        epoch_length=epoch_length,
143        activity_metric=activity_metric,
144        verbosity=verbosity,
145        output_filetype=output_filetype,
146        nonwear_algorithm=nonwear_algorithm,
147    )

Runs main processing steps for wristpy on single files, or directories.

The run() function will execute the run_file() function on individual files, or run_directory() on entire directories. When the input path points to a file, the name of the save file will be taken from the given output path (if any). When the input path points to a directory the output path must be a valid directory as well. Output file names will be derived from original file names in the case of directory processing.

Arguments:
  • input: Path to the input file or directory of files to be read. Currently, this supports .bin and .gt3x
  • output: Path to directory data will be saved to. If processing a single file the path should end in the save file name in either .csv or .parquet formats.
  • thresholds: The cut points for the light, moderate, and vigorous thresholds, given in that order. One threshold tuple must be provided for each activity metric, in the same order the metrics were specified. To use default values for all metrics, leave thresholds as None. Values must be ascending, unique, and greater than 0. Default values are optimized for subjects ages 7-11 [1] [3].
  • calibrator: The calibrator to be used on the input data.
  • epoch_length: The temporal resolution in seconds, the data will be down sampled to. It must be > 0.0.
  • activity_metric: The metric(s) to be used for physical activity categorization. Multiple metrics can be specified as a sequence.
  • nonwear_algorithm: The algorithm to be used for nonwear detection.
  • verbosity: The logging level for the logger.
  • output_filetype: Specifies the data format for the save files. Only used when processing directories.
Returns:

All calculated data in a save ready format as a Results object or as a dictionary of OrchestratorResults objects.

Raises:
  • ValueError: If the number of physical activity thresholds does not match the number of provided activity metrics.
  • ValueError: If the physical activity thresholds are not unique or not in ascending order.
References:

[1] Hildebrand, M., et al. (2014). Age group comparability of raw accelerometer output from wrist- and hip-worn monitors. Medicine and Science in Sports and Exercise, 46(9), 1816-1824. [2] Treuth MS, Schmitz K, Catellier DJ, McMurray RG, Murray DM, Almeida MJ, Going S, Norman JE, Pate R. Defining accelerometer thresholds for activity intensities in adolescent girls. Med Sci Sports Exerc. 2004 Jul;36(7):1259-66. PMID: 15235335; PMCID: PMC2423321. [3] Karas M, Muschelli J, Leroux A, Urbanek J, Wanigatunga A, Bai J, Crainiceanu C, Schrack J Comparison of Accelerometry-Based Measures of Physical Activity: Retrospective Observational Data Analysis Study JMIR Mhealth Uhealth 2022;10(7):e38077 URL: https://mhealth.jmir.org/2022/7/e38077 DOI: 10.2196/38077