Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
92e9319
Implement Pyraview C Engine with Python/Matlab Wrappers
google-labs-jules[bot] Feb 22, 2026
4ccaa3c
Expand Pyraview data types to support int8-int64, uint8-uint64, float…
google-labs-jules[bot] Feb 22, 2026
c62e393
Add CI/CD and Cross-Platform Support
google-labs-jules[bot] Feb 22, 2026
48769e3
Fix CI: Use lukka/get-cmake action
google-labs-jules[bot] Feb 22, 2026
a1d56e2
Add comprehensive tests for C, Python, and Matlab
google-labs-jules[bot] Feb 22, 2026
4d4be7b
Fix MSVC OpenMP loop init and Windows test runner
google-labs-jules[bot] Feb 22, 2026
9a5152c
Add Matlab CI and Fix Windows Builds
google-labs-jules[bot] Feb 22, 2026
4895d85
Add Matlab Toolbox Packaging to CI
google-labs-jules[bot] Feb 22, 2026
f205c92
Fix Windows Test Runner Path Issues
google-labs-jules[bot] Feb 22, 2026
624c79d
Fix Windows CTest paths and runtime linking
google-labs-jules[bot] Feb 22, 2026
7137d30
Force CMake output directories to build/bin
google-labs-jules[bot] Feb 22, 2026
668902e
Fix CMake output directories (build/bin) and GitHub Actions paths
google-labs-jules[bot] Feb 22, 2026
ec15c45
Force build of run_tests executable
google-labs-jules[bot] Feb 22, 2026
af605ec
Revert test target to standard executable
google-labs-jules[bot] Feb 22, 2026
0836cef
Switch Windows CI to MinGW
google-labs-jules[bot] Feb 22, 2026
a8bd07f
Remove explicit MinGW setup action
google-labs-jules[bot] Feb 22, 2026
1b3eaec
Update Matlab CI macos runner
google-labs-jules[bot] Feb 22, 2026
c81ec17
Refactor Matlab tests to function-based suite
google-labs-jules[bot] Feb 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions .github/workflows/build_and_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
name: Build and Release Pyraview

on:
push:
branches: [ main ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]
workflow_dispatch: # Allows manual triggering

jobs:
build_and_test:
name: Build & Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup CMake
uses: lukka/get-cmake@latest

- name: Configure CMake
shell: bash
run: |
mkdir build
if [ "${{ runner.os }}" == "Windows" ]; then
cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
else
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
fi

- name: Build All
run: cmake --build build --parallel

- name: Run C Tests
shell: bash
run: |
cd build
ctest --output-on-failure

# Zip binaries for release (naming by OS/Architecture)
- name: Package Binaries
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
mkdir -p dist
if [ "${{ runner.os }}" == "Windows" ]; then
# Windows CMake builds output to bin due to our CMakeLists.txt fix
zip -j dist/pyraview-win-x64.zip build/bin/*.dll build/bin/*.exe
elif [ "${{ runner.os }}" == "macOS" ]; then
zip -j dist/pyraview-mac-arm.zip build/bin/*.dylib build/bin/run_tests
else
zip -j dist/pyraview-linux-x64.zip build/bin/*.so build/bin/run_tests
fi

- name: Upload Artifacts
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: dist/*

build-matlab:
name: Build & Test Matlab (${{ matrix.os }})
needs: build_and_test
runs-on: ${{ matrix.os }}
strategy:
matrix:
# Matlab actions support limited OS versions, check availability
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4

- uses: matlab-actions/setup-matlab@v2
with:
release: 'R2024b'

- name: Compile MEX
uses: matlab-actions/run-command@v2
with:
# Enable OpenMP if supported by the platform (simple check or flag)
# For Ubuntu (GCC): -fopenmp
# For Windows (MSVC): -openmp (or implied via /openmp)
# For macOS (Clang): -Xpreprocessor -fopenmp -lomp (but requires libomp)
# To keep it simple and avoid linker errors on stock runners without libomp, we skip explicit OMP flags for now or use safe defaults.
# But pyraview.c has #include <omp.h>. If we don't link OMP, it might fail if _OPENMP is defined by default but library isn't linked.
# Let's try compiling WITHOUT flags first, relying on the source's #ifdef _OPENMP guards.
command: mex -v src/matlab/pyraview_mex.c src/c/pyraview.c -Iinclude -output src/matlab/pyraview

- name: Run Matlab Tests
uses: matlab-actions/run-tests@v2
with:
select-by-folder: src/matlab

- name: Upload MEX Artifact
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: mex-${{ matrix.os }}
path: src/matlab/pyraview.* # Matches pyraview.mexw64, .mexa64, etc.
if-no-files-found: error

package-matlab:
name: Package Matlab Toolbox
needs: build-matlab
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: matlab-actions/setup-matlab@v2
with:
release: 'R2024b'

# Download all platform-specific MEX files we just built
- name: Download all MEX artifacts
uses: actions/download-artifact@v4
with:
path: src/matlab
pattern: mex-*
merge-multiple: true

# Run the packaging command
- name: Package Toolbox
uses: matlab-actions/run-command@v2
with:
command: |
opts = matlab.addons.toolbox.ToolboxOptions('toolboxPackaging.prj');
matlab.addons.toolbox.packageToolbox(opts);

# Upload the .mltbx as an artifact so the release job can pick it up
- name: Upload Toolbox Artifact
uses: actions/upload-artifact@v4
with:
name: matlab-toolbox
path: Pyraview.mltbx

release:
name: Create GitHub Release
needs: [build_and_test, package-matlab]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write # Required to create releases

steps:
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true

- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: release-assets/*
name: Release ${{ github.ref_name }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Ignore build artifacts
build/
__pycache__/
*.pyc
*.o
*.so
*.dylib
*.dll
*.a
*.exe
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
Makefile
CTestTestfile.cmake
Testing/

# Ignore test output
test_output*
test_matlab*
test_c*

# Ignore generated files
*.bin
20 changes: 20 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.10)
project(Pyraview)

enable_testing()

# Set global output directories to verify consistent locations
# On Windows, DLLs and EXEs must be in the same folder for runtime linking
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")

# Ensure Windows (MSVC) doesn't append Debug/Release subdirectories
foreach(config ${CMAKE_CONFIGURATION_TYPES} "Release" "Debug" "RelWithDebInfo" "MinSizeRel")
string(TOUPPER "${config}" CONFIG)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG} "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG} "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG} "${CMAKE_BINARY_DIR}/lib")
endforeach()

add_subdirectory(src/c)
70 changes: 70 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Pyraview API Reference

## C API (`src/c/pyraview.c`, `include/pyraview_header.h`)

### `pyraview_process_chunk`
Processes a chunk of raw data and updates decimation pyramids.

Arguments:
- `dataArray`: Pointer to raw data.
- `numRows`: Number of samples per channel.
- `numCols`: Number of channels.
- `dataType`:
- 0: `int8`
- 1: `uint8`
- 2: `int16`
- 3: `uint16`
- 4: `int32`
- 5: `uint32`
- 6: `int64`
- 7: `uint64`
- 8: `float32` (Single)
- 9: `float64` (Double)
- `layout`: 0=SxC (Sample-Major), 1=CxS (Channel-Major).
- `filePrefix`: Base name for output files (e.g., `data/myrecording`).
- `append`: 1=Append, 0=Create/Overwrite.
- `levelSteps`: Pointer to array of decimation factors (e.g., `[100, 10, 10]`).
- `numLevels`: Number of elements in `levelSteps`.
- `nativeRate`: Original sampling rate (Hz).
- `numThreads`: Number of OpenMP threads (0 for auto).

Returns:
- 0 on success.
- Negative values on error (e.g., -2 mismatch, -1 I/O error).

---

## Python API (`src/python/pyraview.py`)

### `process_chunk(data, file_prefix, level_steps, native_rate, append=False, layout='SxC', num_threads=0)`
Wrapper for the C function.

Arguments:
- `data`: Numpy array (2D). Rows=Samples (if SxC), Rows=Channels (if CxS).
- Supported dtypes: `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`.
- `file_prefix`: String path prefix.
- `level_steps`: List of integers.
- `native_rate`: Float.
- `append`: Boolean.
- `layout`: 'SxC' or 'CxS'.
- `num_threads`: Int.

Returns:
- 0 on success. Raises `RuntimeError` on failure.

---

## Matlab API (`src/matlab/pyraview_mex.c`)

### `status = pyraview_mex(data, prefix, steps, nativeRate, [append], [numThreads])`

Arguments:
- `data`: Numeric matrix. Supports: `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `single`, `double`.
- `prefix`: String.
- `steps`: Vector of integers.
- `nativeRate`: Scalar double.
- `append`: Logical/Scalar (optional).
- `numThreads`: Scalar (optional).

Returns:
- `status`: 0 on success. Throws error on failure.
60 changes: 60 additions & 0 deletions docs/BINARY_FORMAT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Pyraview Binary Format

Pyraview uses a simple, efficient binary format for storing multi-resolution time-series data. Each level of the pyramid is stored in a separate file (e.g., `data_L1.bin`, `data_L2.bin`).

## File Structure

| Section | Size | Description |
|---|---|---|
| Header | 1024 bytes | Metadata about the file. |
| Data | Variable | Interleaved Min/Max pairs for all channels. |

## Header (1024 bytes)

The header is fixed-size and little-endian.

| Field | Type | Size | Description |
|---|---|---|---|
| `magic` | char[4] | 4 | "PYRA" magic string. |
| `version` | uint32 | 4 | Format version (currently 1). |
| `dataType` | uint32 | 4 | Enum for data precision (see below). |
| `channelCount` | uint32 | 4 | Number of channels. |
| `sampleRate` | double | 8 | Sample rate of *this level*. |
| `nativeRate` | double | 8 | Original recording rate. |
| `decimationFactor` | uint32 | 4 | Cumulative decimation factor from raw. |
| `reserved` | uint8 | 988 | Padding (zeros). |

### Data Types (Enum)

| Value | Type | Description |
|---|---|---|
| 0 | `int8` | Signed 8-bit integer. |
| 1 | `uint8` | Unsigned 8-bit integer. |
| 2 | `int16` | Signed 16-bit integer. |
| 3 | `uint16` | Unsigned 16-bit integer. |
| 4 | `int32` | Signed 32-bit integer. |
| 5 | `uint32` | Unsigned 32-bit integer. |
| 6 | `int64` | Signed 64-bit integer. |
| 7 | `uint64` | Unsigned 64-bit integer. |
| 8 | `float32` | 32-bit floating point (Single). |
| 9 | `float64` | 64-bit floating point (Double). |

## Data Layout

The data section follows the header immediately (byte 1024).

The layout consists of **Time Chunks**. Each time chunk contains the Min/Max values for all channels for a specific time interval.

Within a chunk, data is organized as:

`[Ch0_MinMax][Ch1_MinMax]...[ChN_MinMax]`

Where `ChX_MinMax` is a sequence of `(Min, Max)` pairs for that channel in that time chunk.

Note: Since files are often appended to, the file consists of a sequence of these chunks. The chunk size depends on the processing block size used during generation.

### Values

Each logical sample in the decimated file consists of TWO values: `Min` and `Max`. They are stored interleaved: `Min, Max, Min, Max...`.

If the file contains `N` logical samples for `C` channels, the total number of values is `N * C * 2`.
Loading