diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..9109adb --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,72 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Extract version from pyproject.toml + id: get_version + run: | + # Extract version from [project] section + VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") + echo "project_version=$VERSION" >> $GITHUB_OUTPUT + echo "Project version: $VERSION" + + - name: Extract tag version + id: get_tag + run: | + # Remove 'refs/tags/' prefix and optional 'v' prefix + TAG_VERSION=${GITHUB_REF#refs/tags/} + TAG_VERSION=${TAG_VERSION#v} + echo "tag_version=$TAG_VERSION" >> $GITHUB_OUTPUT + echo "Tag version: $TAG_VERSION" + + - name: Verify version matches tag + run: | + if [ "${{ steps.get_version.outputs.project_version }}" != "${{ steps.get_tag.outputs.tag_version }}" ]; then + echo "ERROR: Tag version (${{ steps.get_tag.outputs.tag_version }}) doesn't match project version (${{ steps.get_version.outputs.project_version }})" + echo "Please ensure your git tag matches the version in pyproject.toml [project] section" + exit 1 + fi + echo "Version verification passed" + + - name: Install dependencies + run: poetry install --only-root + + - name: Build package + run: poetry build + + - name: Verify build contents + run: | + echo "Built packages:" + ls -la dist/ + echo "Checking wheel contents:" + python -m zipfile -l dist/*.whl | head -20 + + - name: Publish to PyPI + run: poetry publish --username __token__ --password ${{ secrets.PYPI_API_TOKEN }} + + - name: Verify publication + run: | + echo "🎉 Successfully published ensemble-kalman-smoother v${{ steps.get_version.outputs.project_version }} to PyPI!" + echo "Package should be available at: https://pypi.org/project/ensemble-kalman-smoother/${{ steps.get_version.outputs.project_version }}/" diff --git a/eks/__init__.py b/eks/__init__.py index 21fcaa5..30a8e7b 100644 --- a/eks/__init__.py +++ b/eks/__init__.py @@ -1,3 +1,49 @@ +from typing import Any + from eks import * -__version__ = '4.2.0' + +# Hacky way to get version from pypackage.toml. +# Adapted from: https://github.com/python-poetry/poetry/issues/273#issuecomment-1877789967 + +__package_version = "unknown" + + +def __get_package_version() -> str: + """Find the version of this package.""" + global __package_version + + if __package_version != "unknown": + # We already set it at some point in the past, + # so return that previous value without any + # extra work. + return __package_version + + try: + # Try to get the version of the current package if + # it is running from a distribution. + __package_version = importlib.metadata.version("ensemble-kalman-smoother") + except importlib.metadata.PackageNotFoundError: + # Fall back on getting it from a local pyproject.toml. + # This works in a development environment where the + # package has not been installed from a distribution. + import warnings + + import toml + + warnings.warn( + "ensemble-kalman-smoother not pip-installed, getting version from pyproject.toml." + ) + + pyproject_toml_file = Path(__file__).parent.parent / "pyproject.toml" + __package_version = toml.load(pyproject_toml_file)["project"]["version"] + + return __package_version + + +def __getattr__(name: str) -> Any: + """Get package attributes.""" + if name in ("version", "__version__"): + return __get_package_version() + else: + raise AttributeError(f"No attribute {name} in module {__name__}.") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fdc2d06 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,95 @@ +[build-system] +requires = ["poetry-core>=2.0.0"] +build-backend = "poetry.core.masonry.api" + +[project] +name = "ensemble-kalman-smoother" +version = "4.4.0" +description = "Ensembling and kalman smoothing for pose estimation" +license = "MIT" +readme = "README.md" +requires-python = ">=3.10" +authors = [ + { name = "Cole Hurwitz"}, + { name = "Keemin Lee"}, + { name = "Matt Whiteaway" }, +] +maintainers = [ + { name = "Matt Whiteway"}, +] +keywords = ["machine learning", "state space models", "pose estimation"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Image Processing", + "Topic :: Scientific/Engineering :: Visualization", +] + +dependencies = [ + "aniposelib", + "dynamax", + "jax", + "jaxlib", + "matplotlib", + "numpy (>=2.0.0)", + "opencv-python-headless", + "optax", + "pandas", + "scikit-learn", + "scipy (>=1.2.0)", + "sleap_io", + "tqdm", + "typeguard", + "typing", +] + +[project.urls] +repository = "https://github.com/paninski-lab/eks" +documentation = "https://github.com/paninski-lab/eks" + +[tool.poetry] +packages = [ + {include = "eks"} +] + +# project.dependencies are used for metadata when building the project, tool.poetry.dependencies is only used to enrich +# project.dependencies for locking +[tool.poetry.dependencies] +python = ">=3.10,<3.13" + +[project.optional-dependencies] +dev = [ + "black", + "flake8", + "isort", + "pytest", +] + +[tool.flake8] +max-line-length = 99 +ignore = ["F821", "W503"] +extend-ignore = ["E203"] +exclude = [ + ".git", + "__pycache__", + "__init__.py", + "build", + "dist", + "docs/", + "scripts/", +] + +[tool.isort] +line_length = 99 +profile = "black" +src_paths = ["eks", "tests"] + +[tool.pytest.ini_options] +testpaths = "tests" +generate_report_on_test = "True" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 35e012d..0000000 --- a/setup.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length = 99 -ignore = F821, W503 -extend-ignore = E203 -exclude = - .git, - __pycache__, - __init__.py, - build, - dist, - docs/ - -[isort] -line_length = 99 -profile = black \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 6236f0c..0000000 --- a/setup.py +++ /dev/null @@ -1,73 +0,0 @@ -from pathlib import Path - -from setuptools import setup - -# add the README.md file to the long_description -with open('README.md', 'r') as fh: - long_description = fh.read() - - -def read(rel_path): - here = Path(__file__).parent.absolute() - with open(here.joinpath(rel_path), "r") as fp: - return fp.read() - - -def get_version(rel_path): - for line in read(rel_path).splitlines(): - if line.startswith('__version__'): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - else: - raise RuntimeError('Unable to find version string.') - - -# basic requirements -install_requires = [ - 'aniposelib', - 'dynamax', - 'ipykernel', - 'jax', - 'jaxlib', - 'matplotlib', - 'numpy', - 'opencv-python', - 'optax', - 'pandas', - 'scikit-learn', - 'scipy>=1.2.0', - 'sleap_io', - 'tqdm', - 'typeguard', - 'typing', -] - -# additional requirements -extras_require = { - 'dev': { - 'flake8', - 'isort', - 'pytest', - 'Sphinx', - 'sphinx_rtd_theme', - 'sphinx-rtd-dark-mode', - 'sphinx-automodapi', - 'sphinx-copybutton', - 'sphinx-design', - }, -} - - -setup( - name='ensemble-kalman-smoother', - version=get_version(Path('eks').joinpath('__init__.py')), - description='Ensembling and kalman smoothing for pose estimation', - long_description=long_description, - long_description_content_type='text/markdown', - author='Cole Hurwitz', - author_email='', - url='http://www.github.com/paninski-lab/eks', - packages=['eks'], - install_requires=install_requires, - extras_require=extras_require, -) diff --git a/tests/test_multicam_smoother.py b/tests/test_multicam_smoother.py index 59f827b..a134ebf 100644 --- a/tests/test_multicam_smoother.py +++ b/tests/test_multicam_smoother.py @@ -32,6 +32,7 @@ def test_ensemble_kalman_smoother_multicam(): num_fields = len(data_fields) # Create mock MarkerArray with explicit data_fields + np.random.seed(0) markers_array = np.random.randn(n_models, n_cameras, n_frames, len(keypoint_names), num_fields) marker_array = MarkerArray(markers_array, data_fields=data_fields) @@ -164,6 +165,7 @@ def test_ensemble_kalman_smoother_multicam_no_smooth_param(): num_fields = len(data_fields) # Create mock MarkerArray with explicit data_fields + np.random.seed(1) markers_array = np.random.randn(3, n_cameras, n_frames, len(keypoint_names), num_fields) markerArray = MarkerArray(markers_array, data_fields=data_fields) # Ensure data_fields is set