Skip to content

Commit

Permalink
Merge pull request #83 from emotional-cities/package-structure
Browse files Browse the repository at this point in the history
Update build system to use uv
  • Loading branch information
glopesdev authored Feb 13, 2025
2 parents 0852b45 + 07babbd commit 87ceb53
Show file tree
Hide file tree
Showing 17 changed files with 94 additions and 64 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Builds the python environment; linter and formatting via ruff

name: build
on:
push:
branches: ['*']
pull_request:
workflow_dispatch:

jobs:
build_run_tests:
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
if: github.event.pull_request.draft == false
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: [3.9, 3.11]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: pip install -e .[dev]

- name: ruff
run: ruff check .
26 changes: 18 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
.vscode

# Python temp files
__pycache__
*.pyc
*.egg-info
dist
.idea/
# Byte-compiled / optimized / DLL files
__pycache__/

# Distribution / packaging
dist/
_version.py
*.egg-info/
*.egg

# IDE
.vscode/*

# Test
.coverage

# Environment
.venv/
uv.lock
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 NeuroGEARS Ltd
Copyright (c) 2022-2025 NeuroGEARS Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# pluma-analysis

![build](https://github.com/emotional-cities/pluma-analysis/actions/workflows/build.yml/badge.svg?branch=main)

A low-level interface to data collected with the [pluma](https://github.com/emotional-cities/pluma) urban data acquisition system. Data used in the notebooks has been made publicly available in Amazon Simple Storage Service (S3) buckets.

More information about sample data sharing can be found in the [eMOTIONAL Cities data-share repository](https://github.com/emotional-cities/data-share).

## Compatible Environments
## Set-up Instructions

We recommend [uv](https://docs.astral.sh/uv/) for python version, environment, and package dependency management. However, any other tool compatible with the `pyproject.toml` standard should work.

### Install from PyPI

[Visual Studio Code](https://code.visualstudio.com/): All notebooks have been tested in Visual Studio Code on a Windows platform. Tests in other platforms and environments are forthcoming and will be added here.
```
uv pip install pluma-analysis
```

## How to build
### Install from source

1. Open project folder in VS Code
2. Install [miniconda](https://docs.conda.io/en/latest/miniconda.html) (Python 3.9)
3. Install [Python Extension for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
4. Create environment from VS Code:
- `Ctrl+Shift+P` > `Create Environment`
- Select `.conda` environment
5. Make sure correct environment is selected in the notebook
```
git clone https://github.com/emotional-cities/pluma-analysis.git
cd pluma-analysis
uv sync --all-extras
```

The current notebook requires Python 3.9+ to run successfully. The file `environment.yml` contains the list of minimal package dependencies required.
This package was developed for the eMOTIONAL CITIES Project, which received funding from European Union’s Horizon 2020 research and innovation programme, under the grant agreement No 945307. The eMOTIONAL CITIES Project is a consortium of 12 partners co-coordinated by IGOT and FMUL, taking place between 2021 and 2025. More information at https://emotionalcities-h2020.eu/
3 changes: 1 addition & 2 deletions pluma/export/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ def export_stream_to_csv(
resampling_function_kws=resampling_function_kws,
)
else:
raise NotImplementedError(f"Export of stream type\
{type(stream)} is not yet supported.")
raise NotImplementedError(f"Export of stream type {type(stream)} is not yet supported.")


def export_uniform_table_stream(
Expand Down
2 changes: 1 addition & 1 deletion pluma/io/eeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def get_eeg_file(root: Union[str, ComplexPath] = "", if_multiple_load_index: int
ret = None
elif len(expected_files) > 1:
warnings.warn(
f"Multiple *.nedf files found in {root}. " f"Loading {expected_files[if_multiple_load_index]}."
f"Multiple *.nedf files found in {root}. Loading {expected_files[if_multiple_load_index]}."
)
ret = expected_files[if_multiple_load_index]
else:
Expand Down
6 changes: 2 additions & 4 deletions pluma/io/harp.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,10 @@ def read_harp_bin(file: Union[str, ComplexPath], time_offset: float = 0) -> pd.D
with path.open("rb") as stream:
data = np.frombuffer(stream.read(), dtype=np.uint8)
except FileNotFoundError:
warnings.warn(f"Harp stream file\
{path} could not be found.")
warnings.warn(f"Harp stream file {path} could not be found.")
return pd.DataFrame()
except FileExistsError:
warnings.warn(f"Harp stream file\
{path} could not be found.")
warnings.warn(f"Harp stream file {path} could not be found.")
return pd.DataFrame()
if len(data) == 0:
return None
Expand Down
6 changes: 2 additions & 4 deletions pluma/io/microphone.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ def load_microphone(
micdata = np.frombuffer(stream.read(), dtype=dtype)
micdata = micdata.reshape((-1, channels))
except FileNotFoundError:
warnings.warn(f"Microphone stream file\
{path} could not be found.")
warnings.warn(f"Microphone stream file {path} could not be found.")
except FileExistsError:
warnings.warn(f"Microphone stream file\
{path} could not be found.")
warnings.warn(f"Microphone stream file {path} could not be found.")
return micdata
6 changes: 2 additions & 4 deletions pluma/io/ubx.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ def read_ubx_file(path: Union[str, ComplexPath]) -> pd.DataFrame:
with path.open("rb") as fstream:
out = read(fstream)
except FileNotFoundError:
warnings.warn(f"UBX file\
{path} could not be found.")
warnings.warn(f"UBX file {path} could not be found.")
return pd.DataFrame()
except FileExistsError:
warnings.warn(f"UBX file\
{path} could not be found.")
warnings.warn(f"UBX file {path} could not be found.")
return pd.DataFrame()

df = pd.DataFrame({"Message": out})
Expand Down
6 changes: 2 additions & 4 deletions pluma/io/zeromq.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ def load_zeromq(

data_frames.append(pd.DataFrame(np.fromfile(path.path, dtype=np.dtype(dtypes[i]))))
except FileNotFoundError:
warnings.warn(f"Stream file\
{path} could not be found.")
warnings.warn(f"Stream file {path} could not be found.")
except FileExistsError:
warnings.warn(f"Stream file\
{path} could not be found.")
warnings.warn(f"Stream file {path} could not be found.")

return pd.concat(data_frames, axis=1)
4 changes: 2 additions & 2 deletions pluma/preprocessing/ecg.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def heartrate_from_ecg(
Args:
ecg_data (Stream or DataFrame): ECG input data
sample_rate (float, optional): ECG sampling rate (Hz). Defaults to 50.
skip_slice (int, optional): How to slice the incoming raw array. Fs corresponds to the sampling rate post-slicing. Defaults to 4.
skip_slice (int, optional): How to slice the incoming raw array. Fs corresponds to the sampling rate post-slicing. Defaults to 20.
bpmmax (float, optional): Maximum theoretical heartrate. Defaults to 200.0.
highpass_cutoff (float, optional): Cutoff frequency of the high-pass filter (Hz). Defaults to 5.
segment_width (int, optional): Segment window size (in seconds) over which to compute heartrate. Defaults to 5.
Expand All @@ -37,7 +37,7 @@ def heartrate_from_ecg(
if invert:
ecg = ecg * (-1.0)

# sensor acquires at 50hz but saves at 1khz
# sensor acquires at 50hz but is sampled at 1khz
ecg = ecg.Value0[::skip_slice].astype(np.float64)

# high-pass filter seems to give consistently less rejected peaks than notch
Expand Down
3 changes: 1 addition & 2 deletions pluma/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ def _iter_schema_streams(schema: Union[DotMap, Stream, None] = None):
for _nested in Dataset._iter_schema_streams(_stream):
yield _nested
else:
raise TypeError(f"Invalid type was found. Must be of \
{Union[DotMap, Stream]}")
raise TypeError(f"Invalid type was found. Must be of {Union[DotMap, Stream]}")

def reload_streams(self, force_load: bool = False) -> None:
"""Recursively loads, from disk , all available streams in the streams' schema
Expand Down
6 changes: 2 additions & 4 deletions pluma/stream/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ def load(self):
try:
self.data = pd.read_csv(path.path)
except FileNotFoundError:
warnings.warn(f"Glia stream file\
{path} could not be found.")
warnings.warn(f"Glia stream file {path} could not be found.")
except FileExistsError:
warnings.warn(f"Glia stream file\
{path} could not be found.")
warnings.warn(f"Glia stream file {path} could not be found.")

def resample(self):
pass
Expand Down
3 changes: 1 addition & 2 deletions pluma/stream/georeference.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ def _validate_build_spacetime_from_dataframe(self, df: pd.DataFrame) -> bool:
got = [x for x in df.columns]

if not (all(elem in got for elem in expected)):
raise KeyError(f"Not compatible. Expected {expected}\
, and got {got}")
raise KeyError(f"Not compatible. Expected {expected} , and got {got}")
else:
return True

Expand Down
7 changes: 4 additions & 3 deletions pluma/stream/siconversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ def validate_si_meta(self, data: pd.DataFrame) -> bool:
raise AssertionError("No conversion units are set.")

if not (n_handles == n_units == n_col):
raise AssertionError(f"Number of SI conversion handles (={n_handles}),\
units' labels (={n_units}),\
and DataFrame columns (={n_col}) must be the same")
raise AssertionError(
f"Number of SI conversion handles (={n_handles}), units' labels (={n_units}),\
and DataFrame columns (={n_col}) must be the same"
)

def convert_to_si(self, df: pd.DataFrame) -> pd.DataFrame:
if self.is_si:
Expand Down
4 changes: 1 addition & 3 deletions pluma/sync/ubx2harp.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,7 @@ def get_clockcalibration_lookup(
) -> SyncLookup:
# Get the TIM_TM2 Message that timestamps the incoming TTL
if not (ubx_stream.has_event(_UBX_MSGIDS.TIM_TM2)):
raise KeyError(f"UbxStream does not contain \
{_UBX_MSGIDS.TIM_TM2.value}\
event. Try to load it?")
raise KeyError(f"UbxStream does not contain {_UBX_MSGIDS.TIM_TM2.value} event. Try to load it?")

tim_tm2 = ubx_stream.data.TIM_TM2.copy() # TTL
tim_tm2.insert(
Expand Down
14 changes: 5 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,14 @@ classifiers = [

[project.optional-dependencies]
dev = [
"setuptools_scm",
"ruff",
]

[tool.ruff]
line-length = 110

[build-system]
requires = [
"setuptools>=45",
"wheel",
"setuptools_scm[toml]>=6.2",
]
requires = ["setuptools>=64", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
Expand All @@ -66,6 +64,4 @@ include-package-data = true
include = ["pluma*"]

[tool.setuptools_scm]

[tool.ruff]
line-length = 110
version_file = "pluma/_version.py"

0 comments on commit 87ceb53

Please sign in to comment.