Anaropia

This module provides functions to import and preprocess data collected from the Anaropia virtual reality software package.

class balancepy.anaropia.AnaropiaPreprocessingConfig(anaropia_version='legacy', samplingrate_Hz=90, resample=True, end_time_seconds=260, filter_type=None, filter_order=2, filter_cutoff_Hz=8, cut_to_cycles=False, cycle_start_samples=1800, cycle_length_samples=1800)

Bases: object

Configuration for Anaropia data preprocessing.

Unified configuration class for both standard and legacy Anaropia data processing. All parameters have sensible defaults, allowing flexible use for different scenarios.

samplingrate_Hz

Desired sampling rate in Hz. 0 means no resampling. Used for standard Anaropia.

Type:

int, default=90

resample

If True, resample data to samplingrate_Hz.

Type:

bool, default=True

filter_type

Type of filter to apply: ‘low’, ‘high’, ‘band’, or None for no filtering. Zeros phase filtering is applied to avoid phase shifts.

Type:

str, default=None

filter_order

Order of the filter.

Type:

int, default=2

filter_cutoff_Hz

Cutoff frequency for filtering. For ‘band’ filter_type, provide a tuple (low, high).

Type:

float, default=8

cut_to_cycles

If True, cut data to cycles.

Type:

bool, default=True

end_time_seconds

End time of the experiment in seconds for resampling. Standard: 260, Legacy: 220 (adjust as needed).

Type:

float, default=260

cycle_start_samples

Start of the first cycle in samples (20*90 = 1800).

Type:

int, default=1800

cycle_length_samples

Cycle length in samples (20*90 = 1800).

Type:

int, default=1800

Examples

Legacy Anaropia:

>>> config = AnaropiaPreprocessingConfig(stimulus_name='stim_pitch', end_time_seconds=220)
>>> com, time = getdata_legacy('legacy_data.csv', output='com', body_height_m=1.75, config=config)
>>> stim, time = getdata_legacy('legacy_data.csv', output='stimulus', body_height_m=1.75, config=config)

Standard Anaropia:

>>> config = AnaropiaPreprocessingConfig()
>>> com, time = getdata_anaropia('data.csv', output='com', direction='ap', body_height_m=1.75, config=config)
>>> stim_ap, time = getdata_anaropia('data.csv', output='stimulus', direction='ap', body_height_m=1.75, config=config)
>>> stim_ml, time = getdata_anaropia('data.csv', output='stimulus', direction='ml', body_height_m=1.75, config=config)
anaropia_version: str = 'legacy'
cut_to_cycles: bool = False
cycle_length_samples: int = 1800
cycle_start_samples: int = 1800
end_time_seconds: float = 260
filter_cutoff_Hz: float = 8
filter_order: int = 2
filter_type: str = None
resample: bool = True
samplingrate_Hz: int = 90
balancepy.anaropia.batch_iterator(metadata, subjects=None, conditions=None, data_dir='data/raw', skip_nfu=[4, 5, 6])

Iterate over subject-condition combinations in metadata, yielding file paths and configs.

Yields one tuple per valid subject-condition pair, with flexible indexing: - Subjects: row number (int), subject ID (str), or list of either - Conditions: condition number (int, e.g. 1 for “c1: …”), full name (str), or list of either

Missing files indicated by -1 in the metadata cell are skipped silently without warnings.

Parameters:
  • metadata (DataFrame) – Metadata table (typically from get_metadata()).

  • subjects (Union[int, str, List[Union[int, str]], None]) – Subject(s) to process. Can be: - None: all subjects - int: single row number (0-indexed) - str: single subject ID - list: mix of row numbers and/or subject IDs, e.g. [0, ‘MW09’, 2]

  • conditions (Union[int, str, List[Union[int, str]], None]) – Condition(s) to process. Can be: - None: all conditions - int: single condition number (1, 2, 3, …) - str: full condition name (e.g. ‘c1: eyes open’) - list: mix of numbers and/or names, e.g. [1, ‘c2: eyes closed’]

  • data_dir (str) – Data directory relative to parent folder. Default: ‘data/raw’

  • skip_nfu (Optional[List[int]]) – NFU (Not For Use) categories to skip. Entries with matching NFU values will not be yielded. Convention: NFU columns are named “NFU {conditionname}”. Default: [4, 5, 6] (Poor FRF Fit, Marker Missing, File Missing). Set to None to disable NFU filtering.

Yields:
  • subject_id (str) – Subject identifier from metadata

  • condition (str) – Full condition column name (e.g. ‘c1: eyes open’)

  • filepath (str) – Full path to data file

  • config (AnaropiaPreprocessingConfig) – Config with body_height_m set from metadata

Notes

Files marked with -1 in the metadata condition cell are treated as missing and are skipped silently without warning messages.

Entries with NFU (Not For Use) values in the skip_nfu list are also skipped silently. The NFU column is identified by the naming convention “NFU {conditionname}”. By default, NFU categories 4, 5, and 6 are skipped (Poor FRF Fit, Marker Missing, File Missing). Set skip_nfu=None to disable this filtering.

Examples

>>> # All subjects, first 2 conditions (skips NFU 4, 5, 6 by default)
>>> for sid, cond, fp, cfg in batch_iterator(metadata, conditions=[1, 2]):
...     plot_datacheck(fp, cfg)
>>> # Specific subjects by ID, all conditions
>>> for sid, cond, fp, cfg in batch_iterator(metadata, subjects=['MW09', 'AN16']):
...     plot_datacheck(fp, cfg)
>>> # Row 0, condition by name, skip only NFU 6 (file missing)
>>> for sid, cond, fp, cfg in batch_iterator(metadata, subjects=0, conditions='c1: eyes open', skip_nfu=[6]):
...     plot_datacheck(fp, cfg)
>>> # Disable NFU filtering entirely
>>> for sid, cond, fp, cfg in batch_iterator(metadata, subjects=[0, 'MW09'], conditions=[1, 2], skip_nfu=None):
...     # process each file
...     pass
balancepy.anaropia.get_filename_from_metadata(metadata=None, subject=None, condition=None, data_dir='data/raw')

Obtain the data filename from metadata, with intelligent fallback and fuzzy matching.

Retrieves a data filename from metadata by subject and condition, with automatic defaults and fuzzy file matching. If the metadata cell contains just a number, searches for filenames matching: _s{subject_id}_t{number}.csv

Parameters:
  • metadata (DataFrame | None) – Metadata DataFrame. If None, loads default metadata file from ‘../data/metadata.xlsx’.

  • subject (str | int | None) – str → matched against the ‘Subject ID’ column (e.g. 'MW09AB13'). int → used as a zero-based row index (e.g. 0 for first subject). None → defaults to row 0.

  • condition (str | int | None) – str → exact or prefix match against condition column names (e.g. 'c1: eyes open', 'c1'). int → condition number, matched as c{n}... (e.g. 1'c1: ...'). None → uses the first condition column found.

  • data_dir (str) – Directory where data files are located.

Returns:

Full path to the data file.

Return type:

str

Raises:
  • FileNotFoundError – If metadata or data files not found, or if no matching file exists.

  • ValueError – If subject not found, row index out of bounds, or condition not found.

Examples

>>> filepath = get_filename_from_metadata(metadata, 'MW09AB13', 'c1: eyes open')
>>> filepath = get_filename_from_metadata(metadata, 'MW09AB13', 1)
>>> filepath = get_filename_from_metadata(metadata, 0, 'c1: eyes open')
>>> filepath = get_filename_from_metadata(metadata, 0, 1)
balancepy.anaropia.get_metadata(filename=None, print_information=True)

Load and validate metadata from an Excel file.

This function reads metadata containing subject information and experimental conditions, then performs several checks: - Validates that all required columns are present and properly formatted - Extracts condition information (columns starting with ‘c’) - Reports data quality metrics for each condition using NFU (Not For Use) scores

Parameters:

filename (str) – Path to metadata Excel file. If None, loads default file from ‘../data/metadata.xlsx’ relative to current working directory.

Returns:

Metadata table with subject information and condition data.

Return type:

DataFrame

Raises:
  • Exception – If no condition columns are found in the metadata file.

  • Required Columns

  • ----------------

  • The metadata file must contain the following columns:

:raises - Subject ID : Unique identifier for each subject: :raises - body_height_m : Subject height in meters: :raises - body_weight_kg : Subject weight in kilograms: :raises - age_years : Subject age in years: :raises - sex : Subject sex (e.g., ‘m’, ‘w’): :raises Column Naming Conventions: :raises ————————-: :raises Condition columns : Start with ‘c’ (e.g., ‘c1: eyes open’, ‘c2: eyes closed’): :raises NFU columns : Start with ‘NFU ‘ followed by condition name (e.g., ‘NFU c1: eyes open’): :raises NFU (Not For Use) Category Definitions: :raises —————————————-: :raises Score 0 - All Good : The data file is complete, with no irregularities detected.: :raises Score 1 - Big Movements : Excessive participant movements were detected.: :raises Score 2 - >5 HMD Spikes : The data contains more than five spikes in the: Head-Mounted Display (HMD) measurements. :raises Score 3 - Other Irregularities : Any other issues not covered by the above categories: that might affect data quality. :raises Score 4 - Poor FRF Fit : The Frequency Response Function (FRF) fit does not meet: the required criteria. :raises Score 5 - Marker Missing : One or more required markers are missing.: :raises Score 6 - File Missing : The corresponding data file is not available.: :raises Data Quality Grouping: :raises ———————-: :raises - Valid (0): Ready for analysis: :raises - Warnings (1-3): Use with caution, review data quality: :raises - Invalid (4-6): Do not use for analysis:

balancepy.anaropia.getdata_anaropia(filename, output='com', direction='ap', body_height_m=None, body_weight_kg=None, config=None)

Access and format data from balance experiments recorded with Anaropia.

Reads data recorded using the Anaropia virtual-reality application for balance experiments. Calculates stimulus and center of mass (COM) data.

Parameters:
  • filename (str) – Path and filename to be analyzed.

  • output (str) –

    Specifies which data column to return alongside time. - ‘com’ : Center of mass sway (computed from shoulder/hip positions). - ‘stimulus’ : Stimulus signal (column determined by config.stimulus_name

    and the direction parameter).

    • any other str : Raw column name from the data file (e.g. ‘LeftShoulder_pos_z’).

  • body_height_m (float) – Used for center of mass calculations.

  • body_weight_kg (float) – Used for center of pressure calculations.

  • config (AnaropiaPreprocessingConfig) – Configuration object containing processing parameters. If None, uses default configuration with standard settings.

Return type:

tuple

Returns:

  • signal (NDArray) – The requested data array (com, stimulus, or raw column).

  • time (NDArray) – Time data.

See also

AnaropiaPreprocessingConfig

Configuration class for Anaropia data preprocessing

Examples

>>> config = AnaropiaPreprocessingConfig()
>>> com, time = getdata_anaropia('data.csv', output='com', direction='ap', body_height_m=1.75, config=config)
>>> stim_ap, time = getdata_anaropia('data.csv', output='stimulus', direction='ap', body_height_m=1.75, config=config)
>>> stim_ml, time = getdata_anaropia('data.csv', output='stimulus', direction='ml', body_height_m=1.75, config=config)
balancepy.anaropia.plot_datacheck(filename, body_height_m=None, config=None, save_fig=True)

Plot data for comprehensive visual inspection of quality and structure.

Generates a DinA4 landscape-formatted figure with multiple subplots for visually inspecting stimulus and response data using Plotly. The figure includes raw data translations, resampled stimulus and response with cycle indicators, and cycle-by-cycle analysis.

Parameters:
  • filename (str) – Path to the Anaropia data file (CSV format) to be plotted.

  • config (AnaropiaPreprocessingConfig) – Configuration object for data preprocessing. If None, uses default configuration.

  • subject_id (str, optional) – Subject identifier for output filename. If None, uses input filename stem.

  • condition_name (str, optional) – Condition name for output filename. If None, uses input filename stem.

  • output_dir (str, optional) – Directory where the HTML will be saved. If None, displays the figure in browser.

Returns:

Path to the saved HTML file, or None if displayed instead of saved.

Return type:

str

Example

>>> config = AnaropiaPreprocessingConfig(body_height_m=1.75)
>>> plot_datacheck('data/raw/sMW09ON27_t1.csv', config, save_fig=True,
...                subject_id='MW09', condition_name='t1',
...                output_dir='results/')
balancepy.anaropia.run_csmi(filename, body_height_m, body_weight_kg, name=None, config=None)

Run a CSMI (Continuous Sensory Manipulation Identification) analysis for one trial.

Loads the stimulus and COM response from filename, constructs an sr_data object, fits the Peterka18 model, and returns the fitted model object.

Parameters:
  • filename (str) – Path to the Anaropia CSV data file.

  • body_height_m (float) – Subject body height in metres.

  • body_weight_kg (float) – Subject body weight in kilograms.

  • config (AnaropiaPreprocessingConfig) – Preprocessing configuration. Defaults to AnaropiaPreprocessingConfig().

Returns:

subj – Fitted Peterka18 model object with .params, .fit_output, and .plot().

Return type:

Peterka18

Examples

>>> config = bp.AnaropiaPreprocessingConfig()
>>> config.end_time_seconds = 220
>>> config.cut_to_cycles = True
>>> subj = bp.run_csmi(filename, body_height_m=1.75, body_weight_kg=70, config=config)
>>> subj.plot()
balancepy.anaropia.run_csmi_batch(metadata, subjects=None, conditions=None, config=None, n_jobs=-1, plot=False, overwrite_plots=True)

Run CSMI analysis for all subjects and conditions in parallel.

Returns a results DataFrame (Subject ID + fitted parameters) that can be saved independently and merged with metadata as needed. Fitted parameter columns follow the naming convention {param_name}_{condition_slug} (e.g. W_c5_s0_v1).

Parameters:
  • metadata (DataFrame) – Metadata table as returned by get_metadata(). Used for file lookup and to extract body_height_m / body_weight_kg per subject.

  • subjects (Union[int, str, List[Union[int, str]], None]) – Subject selection passed to batch_iterator. None = all subjects.

  • conditions (Union[int, str, List[Union[int, str]], None]) – Condition selection passed to batch_iterator. None = all conditions.

  • config (AnaropiaPreprocessingConfig) – Shared preprocessing config for all trials. Defaults to AnaropiaPreprocessingConfig().

  • n_jobs (int) – Number of parallel workers (passed to joblib.Parallel). -1 uses all available CPUs.

  • plot (bool) – If True, saves a Bode plot for each subject/condition as results/csmi_plots/csmi_{subject_id}_{condition_slug}.pdf.

  • overwrite_plots (bool) – If False, skips saving a plot when the PDF file already exists. Has no effect when plot=False.

Returns:

Results table with ‘Subject ID’ as key plus one column per fitted parameter per condition. One row per subject.

Return type:

DataFrame

Examples

>>> config = bp.AnaropiaPreprocessingConfig()
>>> config.end_time_seconds = 220
>>> config.cut_to_cycles = True
>>> csmi_df = bp.run_csmi_batch(metadata, conditions=['c5: s0_v1', 'c6: s0_v2'], config=config)
>>> csmi_df.to_parquet('../results/csmi_results.parquet', index=False)
>>> metadata = metadata.merge(csmi_df, on='Subject ID', how='left')
balancepy.biomechanics.get_com(shoulder_t, shoulder_marker_height, hip_t, hip_marker_height, height_m, rotation=True)

Calculate center of mass (COM) from hip and shoulder movement.

The function uses a 2-segment model and anthropometric tables.

Parameters:
  • shoulder_t (ndarray[tuple[Any, ...], dtype[number]]) – 1D array of shoulder translation in meters.

  • shoulder_marker_height (number) – Shoulder marker height above support surface in meters. Can be a single value or an array.

  • hip_t (ndarray[tuple[Any, ...], dtype[number]]) – 1D array of hip anterior-posterior translation in meters.

  • hip_marker_height (number) – Hip marker height above support surface in meters. Can be a single value or an array.

  • height_m (float) – Height of subject in meters.

  • rotation (bool) – If False, returns COM translation in meters. If True, returns COM rotation about ankle joints in degrees.

Returns:

1D array of center of mass (COM) values.

Return type:

ndarray[tuple[Any, ...], dtype[TypeVar(_ScalarT, bound= generic)]]