# cfs2mat

[![PyPI version](https://img.shields.io/pypi/v/cfs2mat.svg)](https://pypi.org/project/cfs2mat/)
[![Python versions](https://img.shields.io/pypi/pyversions/cfs2mat.svg)](https://pypi.org/project/cfs2mat/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Convert [CED Signal](https://ced.co.uk/) `.cfs` files to MATLAB `.mat` format with full metadata preservation.

CFS (CED Filing System) is the native binary format used by Cambridge Electronic Design's **Signal** software for sweep-based neurophysiology recordings (EMG, EEG, evoked potentials, etc.). This tool converts `.cfs` files into `.mat` files that can be loaded directly in MATLAB, preserving all channel metadata, sweep structure, events, and calibration info.

## Prerequisites

`cfs2mat` uses [biosig](https://biosig.sourceforge.net/) to read CFS files. The `biosig` Python package requires the `libbiosig` C library on your system:

```bash
# macOS
brew install biosig

# Linux (Debian/Ubuntu)
sudo apt install libbiosig-dev

# Linux (Fedora)
sudo dnf install biosig-devel
```

## Installation

```bash
# With uv (recommended)
uv tool install cfs2mat

# With pip
pip install cfs2mat
```

## CLI Usage

### Convert files

```bash
# Single file
cfs2mat convert recording.cfs

# Multiple files / glob patterns
cfs2mat convert *.cfs
cfs2mat convert data/**/*.cfs

# Output to a specific directory
cfs2mat convert *.cfs -o converted/

# Store data flat (samples x channels) without sweep reshaping
cfs2mat convert recording.cfs --flat

# Quiet mode (no summary output)
cfs2mat convert recording.cfs -q
```

### Inspect file metadata

```bash
# Human-readable summary
cfs2mat info recording.cfs

# Full header as JSON
cfs2mat info recording.cfs --json
```

Example `info` output:

```
File:       recording.cfs
Type:       CFS v6.06
Recorded:   2026-02-07 09:13:18.999991
Rate:       2000.00 Hz
Sweeps:     10
Samples:    8000 total (800/sweep)
Channels:   6
  [1] EMG1          unit=V     rate=2000.0 Hz  scale=0.000152588
  [2] EMG2          unit=V     rate=2000.0 Hz  scale=0.000152588
  [3] EMG3          unit=V     rate=2000.0 Hz  scale=0.000152588
  [4] EMG4          unit=V     rate=2000.0 Hz  scale=0.000152588
  [5] Marker        unit=s     rate=2000.0 Hz  scale=0.0005
  [6] Keyboard      unit=?     rate=2000.0 Hz  scale=1
Events:     9
  2026-02-07 09:14:02.592794      start of a new segment (after a break)
  ...
```

## Python API

```python
from cfs2mat import cfs_to_mat, read_cfs

# Convert to .mat (returns output path)
cfs_to_mat("recording.cfs")
cfs_to_mat("recording.cfs", mat_path="output.mat", flat=True)

# Read header + data without converting
header, data = read_cfs("recording.cfs")
# header: dict with all CFS metadata
# data: numpy array (samples x channels)
```

## Output `.mat` Structure

The output `.mat` file can be loaded in MATLAB with `load('recording.mat')` and contains:

### Data

| Variable | Shape | Description |
|---|---|---|
| `data` | `(sweeps, samples_per_sweep, channels)` | Waveform data reshaped by sweep |
| `data_flat` | `(total_samples, channels)` | Same data, all sweeps concatenated |

### Recording metadata

| Variable | Description |
|---|---|
| `sampling_rate` | Global sampling rate (Hz) |
| `n_sweeps` | Number of sweeps |
| `n_channels` | Number of channels |
| `n_samples` | Total sample count |
| `samples_per_sweep` | Samples per sweep |
| `start_of_recording` | ISO timestamp string |
| `file_type` | Format identifier (`CFS`) |
| `file_version` | CFS version number |

### Channel metadata (arrays indexed by channel)

| Variable | Description |
|---|---|
| `channel_labels` | Channel names (e.g. `EMG1`, `Marker`) |
| `channel_units` | Physical units (`V`, `s`, etc.) |
| `channel_sampling_rates` | Per-channel sampling rate |
| `channel_scaling` | Scale factor |
| `channel_offset` | Offset value |
| `channel_physical_max/min` | Physical range |
| `channel_digital_max/min` | Digital range |

### Events

| Variable | Description |
|---|---|
| `event_types` | Event type codes |
| `event_positions` | Event positions (seconds) |
| `event_timestamps` | ISO timestamp per event |
| `event_descriptions` | Human-readable event labels |

### Other

| Variable | Description |
|---|---|
| `patient_id`, `patient_gender` | Patient demographics |
| `manufacturer` | Device manufacturer string |
| `header_json` | Complete original CFS header as a JSON string |

## MATLAB Example

```matlab
d = load('recording.mat');

% Plot first sweep, first channel
plot(squeeze(d.data(1, :, 1)));
title(d.channel_labels{1});
ylabel(d.channel_units{1});
xlabel(sprintf('Samples @ %.0f Hz', d.sampling_rate));

% Access all metadata
disp(d.start_of_recording);
disp(d.channel_labels);
```

## License

MIT
