Skip to content

Commit fb94920

Browse files
committed
First commit with code and github workflows for unit tests and releasing
1 parent 5175335 commit fb94920

File tree

12 files changed

+1095
-5
lines changed

12 files changed

+1095
-5
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve. For feature requests, please go to the Discussions instead.
4+
title: ''
5+
assignees: ''
6+
7+
---
8+
9+
**Describe the bug**
10+
A clear and concise description of what the bug is.
11+
12+
**To Reproduce**
13+
Steps to reproduce the behavior:
14+
1. Go to '...'
15+
2. Click on '....'
16+
3. Scroll down to '....'
17+
4. See error
18+
19+
**Expected behavior**
20+
A clear and concise description of what you expected to happen.
21+
22+
23+
**Traceback**
24+
If applicable, copy/paste the traceback here. The traceback is the complete error message, starting by the line `Traceback (most recent call last):` all the way to the end.
25+
26+
**Screenshots**
27+
If applicable, add screenshots to help explain your problem.
28+
29+
**Python installation**
30+
Select: python.org, Anaconda, conda-forge, etc.
31+
32+
**Operating system**
33+
- OS: [e.g. macOS, Windows, Linux]
34+
- Version [e.g. Big Sur, 10]
35+
36+
**Additional context**
37+
Add any other context about the problem here.
38+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Publish Package
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v2
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: '3.11'
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip setuptools wheel twine
23+
24+
- name: Build and publish to PyPI
25+
env:
26+
TWINE_USERNAME: __token__
27+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
28+
run: |
29+
python setup.py sdist bdist_wheel
30+
twine upload dist/*

.github/workflows/run_tests.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
name: run_tests
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
workflow_dispatch:
12+
13+
jobs:
14+
unittests:
15+
defaults:
16+
run:
17+
shell: bash -l {0}
18+
19+
runs-on: ${{ matrix.os }}
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
python-version: ["3.10", "3.11", "3.12", "3.13"]
24+
os: [ubuntu-latest, windows-latest, macos-latest]
25+
26+
steps:
27+
- uses: actions/checkout@v4
28+
- name: Setup conda
29+
uses: conda-incubator/setup-miniconda@v3
30+
with:
31+
activate-environment: anaconda-client-env
32+
- name: Install dependencies
33+
run: |
34+
conda install -c conda-forge python=${{ matrix.python-version }} mamba pytest -y
35+
mamba install -c conda-forge kineticstoolkit -y
36+
- name: Test with pytest and crash on warnings (macos)
37+
if: matrix.os == 'macos-latest'
38+
run: |
39+
export PYTHONPATH=":kineticstoolkit_extensions"
40+
echo "Running tests with PYTHONPATH=$PYTHONPATH"
41+
pytest tests -W error::RuntimeWarning
42+
- name: Test with pytest and crash on warnings (linux)
43+
if: matrix.os == 'ubuntu-latest'
44+
run: |
45+
export PYTHONPATH=":kineticstoolkit_extensions"
46+
echo "Running tests with PYTHONPATH=$PYTHONPATH"
47+
pytest tests -W error::RuntimeWarning
48+
- name: Test with pytest and crash on warnings (windows)
49+
if: matrix.os == 'windows-latest'
50+
run: |
51+
export PYTHONPATH=";kineticstoolkit_extensions"
52+
echo "Running tests with PYTHONPATH=$PYTHONPATH"
53+
pytest tests -W error::RuntimeWarning

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ Additional modules and development of new features for Kinetics Toolkit
33

44
This repository will replace the different repositories used for the current extension system. It will also be distributed via pip and conda, like kineticstoolkit.
55

6-
It will contain features that are deemed too specific for Kinetics Toolkit, but still useful in some use cases. It will also contain new features in developement, before the features are integrated in the kineticstoolkit core. The main objective for this method is to:
6+
It will contain features that are deemed too specific for Kinetics Toolkit, but still useful in some use cases. It will also contain half-baked new features in development. The main objective for this method is to:
77

8-
- reach a stable 1.0 version for Kinetics Toolkit
9-
- continue developing new features with a clear separation between development/testing (here) and stable (core)
10-
- ease the installation of extensions, without adding unnecessary friction (i.e. maintaining multiple repositories)
11-
- adopt continuous integration practices for extensions as for kineticstoolkit (i.e. unit tests)
8+
- reach a stable 1.0 version for Kinetics Toolkit while continuing developing new features with a clear separation between misc/development/testing (kineticstoolkit_extensions) and stable (kineticstoolkit)
9+
- ease the installation of extensions, without adding unnecessary friction both for developers and users (i.e. maintaining multiple repositories)
10+
- adopt continuous integration practices (i.e. unit tests) for extensions, like for kineticstoolkit

data/n3d_sample_optotrak.n3d

67.5 KB
Binary file not shown.

kineticstoolkit_extensions/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.master
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2020-2025 Félix Chénier
5+
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
"""Kinetics Toolkit Extensions."""
18+
__author__ = "Félix Chénier"
19+
__copyright__ = "Copyright (C) 2020-2025 Félix Chénier"
20+
__email__ = "[email protected]"
21+
__license__ = "Apache 2.0"
22+
23+
import os
24+
25+
import kineticstoolkit_extensions.n3d as n3d
26+
import kineticstoolkit_extensions.pushrimkinetics as pushrimkinetics
27+
28+
29+
def __dir__():
30+
return ["n3d", "pushrimkinetics"]
31+
32+
33+
root_folder = os.path.dirname(os.path.dirname(__file__))

kineticstoolkit_extensions/n3d.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2022 Félix Chénier
5+
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
19+
"""
20+
Provide a short description of your extension.
21+
"""
22+
23+
__author__ = "Félix Chénier"
24+
__copyright__ = "Copyright (C) 2022 Félix Chénier"
25+
__email__ = "[email protected]"
26+
__license__ = "Apache 2.0"
27+
28+
29+
from kineticstoolkit import TimeSeries
30+
import struct
31+
import numpy as np
32+
from typing import Sequence
33+
34+
35+
def read_n3d(filename: str, labels: Sequence[str] = []) -> TimeSeries:
36+
"""
37+
Read markers from an NDI N3D file.
38+
39+
The markers positions are returned in a TimeSeries where each marker
40+
corresponds to a data key. Each marker position is expressed in this form:
41+
42+
array([[x0, y0, z0, 1.], [x1, y1, z1, 1.], [x2, y2, z2, 1.], ...])
43+
44+
Parameters
45+
----------
46+
filename : str
47+
Path of the N3D file.
48+
labels : list of str (optional)
49+
Marker names
50+
51+
Returns
52+
-------
53+
TimeSeries
54+
55+
"""
56+
with open(filename, "rb") as fid:
57+
_ = fid.read(1) # 32
58+
n_markers = struct.unpack("h", fid.read(2))[0]
59+
n_data_per_marker = struct.unpack("h", fid.read(2))[0]
60+
n_columns = n_markers * n_data_per_marker
61+
62+
n_frames = struct.unpack("i", fid.read(4))[0]
63+
64+
collection_frame_frequency = struct.unpack("f", fid.read(4))[0]
65+
user_comments = struct.unpack("60s", fid.read(60))[0]
66+
system_comments = struct.unpack("60s", fid.read(60))[0]
67+
file_description = struct.unpack("30s", fid.read(30))[0]
68+
cutoff_filter_frequency = struct.unpack("h", fid.read(2))[0]
69+
time_of_collection = struct.unpack("8s", fid.read(8))[0]
70+
_ = fid.read(2)
71+
date_of_collection = struct.unpack("8s", fid.read(8))[0]
72+
extended_header = struct.unpack("73s", fid.read(73))[0]
73+
74+
# Read the rest and put it in an array
75+
ndi_array = np.ones((n_frames, n_columns)) * np.NaN
76+
77+
for i_frame in range(n_frames):
78+
for i_column in range(n_columns):
79+
data = struct.unpack("f", fid.read(4))[0]
80+
if data < -1e25: # technically, it is -3.697314e+28
81+
data = np.NaN
82+
ndi_array[i_frame, i_column] = data
83+
84+
# Conversion from mm to meters
85+
ndi_array /= 1000
86+
87+
# Transformation to a TimeSeries
88+
ts = TimeSeries(
89+
time=np.linspace(
90+
0, n_frames / collection_frame_frequency, n_frames
91+
)
92+
)
93+
94+
for i_marker in range(n_markers):
95+
if labels != []:
96+
label = labels[i_marker]
97+
else:
98+
label = f"Marker{i_marker}"
99+
100+
ts.data[label] = np.block(
101+
[
102+
[
103+
ndi_array[:, 3 * i_marker : 3 * i_marker + 3],
104+
np.ones((n_frames, 1)),
105+
]
106+
]
107+
)
108+
ts = ts.add_data_info(label, "Unit", "m")
109+
110+
return ts
111+
112+
113+
"""
114+
The section below is optional. If you put code examples in your docstring,
115+
then running this file will test that your examples give the correct results.
116+
Please check https://docs.python.org/3/library/doctest.html for details.
117+
"""
118+
if __name__ == "__main__": # pragma: no cover
119+
import doctest
120+
121+
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)

0 commit comments

Comments
 (0)