diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0105392..0000000 --- a/.coveragerc +++ /dev/null @@ -1,15 +0,0 @@ -# .coveragerc to control coverage.py -[report] -# Regexes for lines to exclude from consideration -exclude_lines = - except ImportError - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - -ignore_errors = True -fail_under = 45 - -# show which lines are missing -show_missing = False diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a82fa2c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,61 @@ +name: Wheel Builder + +on: + workflow_call: + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build_wheels: + name: Build ${{ github.repository }} wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-12] + + steps: + - name: Checkout ${{ github.repository }} + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up QEMU (For Linux ARM) + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v2 + with: + platforms: arm64 + + - name: Build Wheels + uses: pypa/cibuildwheel@v2.11.4 + with: + package-dir: . + output-dir: wheelhouse + config-file: "{package}/pyproject.toml" + + - name: Store Wheel Artifacts + uses: actions/upload-artifact@v3 + with: + name: distributions + path: wheelhouse/*.whl + + build_sdist: + name: Build ${{ github.repository }} Source Distribution + runs-on: ubuntu-latest + steps: + - name: Checkout ${{ github.repository }} + uses: actions/checkout@v3 + + - name: Build sdist + run: pipx run build --sdist + + - name: Check sdist Metadata + run: pipx run twine check dist/* + + - name: Store sdist Artifacts + uses: actions/upload-artifact@v3 + with: + name: distributions + path: dist/*.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b588f7c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [labeled, opened, synchronize, reopened] + workflow_dispatch: + merge_group: + types: [checks_requested] + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + test: + name: Test + permissions: + contents: read + pull-requests: write + secrets: inherit + uses: ./.github/workflows/test.yml + + build: + name: Build + permissions: + contents: read + pull-requests: write + secrets: inherit + uses: ./.github/workflows/build.yml + + upload_coverage: + needs: [test] + name: Upload Coverage + runs-on: ubuntu-latest + steps: + - name: Checkout ${{github.repository }} + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download Coverage Artifact + uses: actions/download-artifact@v3 + with: + name: coverage + path: coverage + + - name: Upload Coverage to Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ${{ github.workspace }}/coverage/coverage.lcov diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml new file mode 100644 index 0000000..be46f10 --- /dev/null +++ b/.github/workflows/pypi_publish.yml @@ -0,0 +1,71 @@ +name: Build Wheels and upload to PyPI + +on: + pull_request: + branches: ["releases/**"] + types: [labeled, opened, synchronize, reopened] + release: + types: [published] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + test: + name: Test + permissions: + contents: read + secrets: inherit + uses: ./.github/workflows/test.yml + + build_wheels_sdist: + needs: [test] + name: Build + uses: ./.github/workflows/build.yml + secrets: inherit + + test_pypi_publish: + # Test PyPI publish, requires wheels and source dist (sdist) + name: Publish ${{ github.repository }} to TestPyPI + needs: [test, build_wheels_sdist] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: distributions + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1.6 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + packages_dir: dist/ + verbose: true + + pypi_publish: + name: Publish ${{ github.repository }} to to PyPI + needs: [test, build_wheels_sdist, test_pypi_publish] + + runs-on: ubuntu-latest + # Publish when a GitHub Release is created, use the following rule: + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - name: Download Artifact + uses: actions/download-artifact@v3 + with: + name: distributions + path: dist + + - name: PYPI Publish + uses: pypa/gh-action-pypi-publish@release/v1.6 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: dist/ + verbose: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..73d8027 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: Test + +on: + workflow_call: + +permissions: + contents: read # to fetch code (actions/checkout) +jobs: + test: + name: Test ${{ github.repository }} + runs-on: ubuntu-latest + + steps: + - name: Checkout ${{ github.repository }} + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + cache-dependency-path: "**/pyproject.toml" + cache: "pip" + + - name: Install Dependencies and ${{ github.repository }} + run: | + pip install .[test] + + - name: Run Tests + run: | + pytest + + - name: Archive Coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: | + coverage.lcov diff --git a/.gitignore b/.gitignore index 104770f..2c19678 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ mibi_bin_tools/*.so *.egg-info .coverage htmlcov/ -.DS_Store \ No newline at end of file +.DS_Store +.venv \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9847130..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -dist: xenial - -git: - depth: false - -language: python - -python: - - 3.8 - -install: - - travis_retry pip install -r requirements.txt - - travis_retry pip install -r requirements-test.txt - -env: - - MPLBACKEND=Agg - -cache: pip - -script: - - python -m pip install --editable . - - python -m pytest --cov=mibi_bin_tools --pycodestyle tests mibi_bin_tools - -after_success: - - coveralls \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8ed8cdd..0000000 --- a/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM python:3.8 - -# system maintenance -RUN apt-get update && apt-get install -y gcc - -WORKDIR /scripts - -# copy over the requirements.txt, install dependencies, and README -COPY docker-requirements.txt /opt/mibi-bin-tools/ -RUN pip install -r /opt/mibi-bin-tools/docker-requirements.txt - -COPY requirements.txt /opt/mibi-bin-tools/ -RUN pip install -r /opt/mibi-bin-tools/requirements.txt - -# copy the scripts over -COPY setup.py /opt/mibi-bin-tools/ -COPY mibi_bin_tools /opt/mibi-bin-tools/mibi_bin_tools - -COPY README.md /opt/mibi-bin-tools/ - -# Install the package via setup.py -RUN pip install /opt/mibi-bin-tools - -# jupyter lab -CMD jupyter lab --ip=0.0.0.0 --allow-root --no-browser \ No newline at end of file diff --git a/docker-requirements.txt b/docker-requirements.txt deleted file mode 100644 index 6e4af14..0000000 --- a/docker-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter>=1.0.0,<2 -jupyter_contrib_nbextensions>=0.5.1,<1 -jupyterlab>=3.1.5,<4 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 900b87f..21a31b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,135 @@ [build-system] -requires = ["setuptools", "oldest-supported-numpy", "Cython>=0.29.24,<1"] -build-backend = "setuptools.build_meta" \ No newline at end of file +requires = [ + "setuptools", + "wheel", + "Cython>=0.29.24,<1", + "oldest-supported-numpy", + "setuptools_scm[toml]>=6.2", +] +build-backend = "setuptools.build_meta" + +[project] +dependencies = [ + "Cython>=0.29.24", + "matplotlib>=3", + "numpy>=1.2", + "pandas>=1.3", + "scikit-image>=0.19", + "alpineer>=0.1.5", + "xarray>=2022" +] +name = "mibi-bin-tools" +authors = [{name = "Angelo Lab", email = "theangelolab@gmail.com"}] +description = "Source for extracting .bin files from the commercial MIBI." +readme = "README.md" +requires-python = ">=3.8" +license = {text = "Modified Apache License 2.0"} +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Cython", + "Programming language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "License :: OSI Approved :: Apache Software License", + "Topic :: Scientific/Engineering :: Bio-Informatics", +] +dynamic = ["version"] +urls = {repository = "https://github.com/angelolab/mibi-bin-tools"} + +[project.optional-dependencies] +test = [ + "coveralls[toml]", + "pytest", + "pytest-cases", + "pytest-cov", + "pytest-mock", + "pytest-pycodestyle" +] + +[tool.setuptools_scm] +version_scheme = "release-branch-semver" +local_scheme = "no-local-version" + +[tool.cibuildwheel] +build = ["cp38-*"] +skip = [ + "cp36-*", # Python 3.6 + "cp37-*", # Python 3.7 + "cp39-*", # Python 3.9 + "cp310-*", # Python 3.10 + "cp311-*", # Python 3.11 + "*-musllinux_*", # Musllinux + "pp*", # PyPy wheels on all platforms + "*_i686", # 32bit Linux Wheels + "*_s390x", # IBM System/390, "mainframe" + "*-win32", # 32bit Windows Wheels + "*_ppc64le", # PowerPC +] + +build-frontend = "build" +build-verbosity = 3 + +# Avoid testing on emulated architectures +test-skip = [ + "*-win_arm64", # Skip testing emulated arm64 biulds on Windows + "*-*linux_aarch64", # Skip testing emulated Linux builds + "*-macosx_arm64", # Skip testing emulated arm64 builds on Intel Macs + "*-macosx_universal2:arm64", # Skip testing emulated arm64 portion of universal2 builds +] + +# "manylinux" versioning +# PEP 600: https://peps.python.org/pep-0600/ +# Build using the manylinux_2_28 image +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" + + +# On an Linux Intel runner with qemu installed, build Intel and aarch64 (arm) wheels +[tool.cibuildwheel.linux] +archs = ["x86_64", "aarch64"] +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" + +# Build `universal2` and `arm64` wheels on an Intel runner. +# Note that the `arm64` wheel and the `arm64` part of the `universal2` wheel cannot be tested in this configuration. +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64", "universal2"] +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" + +# Build for Windows x86_64, and ARM 64 +[tool.cibuildwheel.windows] +archs = ["AMD64", "ARM64"] +# might not need to repair with delvewheel? +# before-build = "pip install delvewheel" # Use delvewheel on windows +# repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" + +# Coverage +[tool.coverage.paths] +source = ["src", "*/site-packages"] + +[tool.coverage.run] +branch = true +source = ["mibi_bin_tools"] + +[tool.coverage.report] +exclude_lines = [ + "except ImportError", + "raise AssertionError", + "raise NotImplementedError", + "(ArrowInvalid, OSError, IOError)", +] + +# Pytest Options +[tool.pytest.ini_options] +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::PendingDeprecationWarning", +] +addopts = [ + "-v", + "-s", + "--durations=20", + "--cov=mibi_bin_tools", + "--cov-report=lcov", + "--pycodestyle", +] +console_output_style = "count" +testpaths = ["tests"] diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 104fdcd..0000000 --- a/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -coveralls>=3.3.1,<4.0 -pytest>=7.1.2,<8.0.0 -pytest-cov>=3.0.0,<4 -pytest-mock<4.0.0 -pytest-pycodestyle>=2.3.0,<3.0 -pytest-cases>=3.6.0,<4 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 3d59196..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -Cython>=0.29 -matplotlib>=3 -numpy>=1.2 -pandas>=1.3 -scikit-image>=0.19 -tmi @ git+https://github.com/angelolab/tmi.git@v0.1.2 -xarray>=2022 diff --git a/setup.py b/setup.py index 756acfd..43ef95f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ -from os import path, pardir -from setuptools import setup, find_packages, Extension +from os import pardir, path +from setuptools import setup, Extension import numpy as np from Cython.Build import cythonize @@ -7,54 +7,24 @@ if CYTHON_DEBUG: from Cython.Compiler.Options import get_directive_defaults + directive_defaults = get_directive_defaults() + directive_defaults["linetrace"] = True + directive_defaults["binding"] = True - directive_defaults['linetrace'] = True - directive_defaults['binding'] = True +CYTHON_MACROS = [("CYTHON_TRACE", "1")] if CYTHON_DEBUG else None -CYTHON_MACROS = [('CYTHON_TRACE', '1')] if CYTHON_DEBUG else None - -VERSION = '0.2.4' - -PKG_FOLDER = path.abspath(path.join(__file__, pardir)) - -with open(path.join(PKG_FOLDER, 'requirements.txt')) as req_file: - requirements = req_file.read().splitlines() - -# set a long description which is basically the README -with open(path.join(PKG_FOLDER, 'README.md')) as f: - long_description = f.read() +PKG_FOLDER = path.relpath(path.join(__file__, pardir)) extensions = [ Extension( name="mibi_bin_tools._extract_bin", - sources=[path.join(PKG_FOLDER, "mibi_bin_tools/_extract_bin.pyx")], + sources=[path.join(PKG_FOLDER, "src", "mibi_bin_tools", "_extract_bin.pyx")], include_dirs=[np.get_include()], define_macros=CYTHON_MACROS ) ] setup( - name='mibi-bin-tools', - version=VERSION, - packages=find_packages(), - license='Modified Apache License 2.0', - description='Scripts for extracting .bin files from the commercial MIBI instrument', - author='Angelo Lab', - url='https://github.com/angelolab/mibi-bin-tools', - download_url=f'https://github.com/angelolab/mibi-bin-tools/archive/v{VERSION}.tar.gz', ext_modules=cythonize(extensions, compiler_directives={'language_level': "3"}), - install_requires=requirements, - extras_require={ - 'tests': ['pytest', - 'pytest-cov', - 'pytest-pycodestyle', - 'testbook'] - }, - long_description=long_description, - long_description_content_type='text/markdown', - classifiers=['License :: OSI Approved :: Apache Software License', - 'Development Status :: 4 - Beta', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6'] ) diff --git a/mibi_bin_tools/__init__.py b/src/mibi_bin_tools/__init__.py similarity index 100% rename from mibi_bin_tools/__init__.py rename to src/mibi_bin_tools/__init__.py diff --git a/mibi_bin_tools/_extract_bin.pyx b/src/mibi_bin_tools/_extract_bin.pyx similarity index 100% rename from mibi_bin_tools/_extract_bin.pyx rename to src/mibi_bin_tools/_extract_bin.pyx diff --git a/mibi_bin_tools/bin_files.py b/src/mibi_bin_tools/bin_files.py similarity index 99% rename from mibi_bin_tools/bin_files.py rename to src/mibi_bin_tools/bin_files.py index af58ab7..ecb5d53 100644 --- a/mibi_bin_tools/bin_files.py +++ b/src/mibi_bin_tools/bin_files.py @@ -8,7 +8,7 @@ import xarray as xr from mibi_bin_tools import type_utils, _extract_bin -from tmi import io_utils, image_utils +from alpineer import io_utils, image_utils def _mass2tof(masses_arr: np.ndarray, mass_offset: float, mass_gain: float, diff --git a/mibi_bin_tools/panel_utils.py b/src/mibi_bin_tools/panel_utils.py similarity index 98% rename from mibi_bin_tools/panel_utils.py rename to src/mibi_bin_tools/panel_utils.py index 3dbe86a..0c89eba 100644 --- a/mibi_bin_tools/panel_utils.py +++ b/src/mibi_bin_tools/panel_utils.py @@ -1,7 +1,7 @@ from typing import Union, List import pandas as pd -from tmi import misc_utils +from alpineer import misc_utils def make_panel(mass: Union[float, List[float]], diff --git a/mibi_bin_tools/type_utils.py b/src/mibi_bin_tools/type_utils.py similarity index 91% rename from mibi_bin_tools/type_utils.py rename to src/mibi_bin_tools/type_utils.py index ced0758..06134fa 100644 --- a/mibi_bin_tools/type_utils.py +++ b/src/mibi_bin_tools/type_utils.py @@ -1,5 +1,5 @@ from typing import Union, Iterable -from tmi import misc_utils +from alpineer import misc_utils def any_true(a: Union[bool, Iterable[bool]]) -> bool: diff --git a/start_docker.sh b/start_docker.sh deleted file mode 100644 index f099a1b..0000000 --- a/start_docker.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -# check for template developer flag -JUPYTER_DIR='scripts' -update=0 -mount='' -while test $# -gt 0 -do - case "$1" in - -u|--update) - update=1 - shift - ;; - -m|--mount) - mount="$2" - shift - shift - ;; - *) - echo "$1 is not an accepted option..." - echo "-u, --update : Update default scripts" - echo "-m, --mount : Mount external drives to /data" - exit - ;; - esac -done - -# if requirements.txt has been changed in the last day, automatically rebuild Docker first -if [[ $(find . -mmin -1440 -type f -print | grep requirements.txt | wc -l) -eq 1 ]] - then - echo "New requirements.txt file detected, rebuilding Docker" - docker build -t mibi-bin-tools . -fi - -if [ ! -f "$PWD/$JUPYTER_DIR" ] - then - update=1 -fi -if [ $update -ne 0 ] - then - bash update_notebooks.sh -u - else - bash update_notebooks.sh -fi - -# find lowest open port available -PORT=8888 - -until [[ $(docker container ls | grep 0.0.0.0:$PORT | wc -l) -eq 0 ]] - do - ((PORT=$PORT+1)) -done - -if [ ! -z "$mount" ] - then - docker run -it \ - -p $PORT:$PORT \ - -v "$PWD/$JUPYTER_DIR:/$JUPYTER_DIR" \ - -v "$mount:/data/" \ - mibi-bin-tools:latest - else - echo "must provide a -m or --mount argument..." - exit -fi diff --git a/templates/a.ipynb b/templates/a.ipynb deleted file mode 100644 index aebfec7..0000000 --- a/templates/a.ipynb +++ /dev/null @@ -1,30 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "from mibi_bin_tools import bin_files, io_utils" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "language_info": { - "name": "python" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bin_files_test.py b/tests/bin_files_test.py index 196c961..8f2af5c 100644 --- a/tests/bin_files_test.py +++ b/tests/bin_files_test.py @@ -10,7 +10,7 @@ import pandas as pd from mibi_bin_tools import bin_files, type_utils, _extract_bin -from tmi import io_utils +from alpineer import io_utils THIS_DIR = Path(__file__).parent diff --git a/tox.ini b/tox.ini index 65e19de..ee2a70b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,3 @@ -# Configuration of py.test -[pytest] -addopts=-v - -s - --durations=20 - -# Ignore Deprecation Warnings -filterwarnings = - ignore::DeprecationWarning - ignore::PendingDeprecationWarning - - # Enable line length testing with maximum line length of 99 [pycodestyle] -max-line-length = 99 \ No newline at end of file +max-line-length = 99 diff --git a/update_notebooks.sh b/update_notebooks.sh deleted file mode 100644 index f4a7fc7..0000000 --- a/update_notebooks.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# behavior depends on scripts directory's presence -if [ -d "$PWD/scripts" ] -then - - # check for update flag - update=0 - while test $# -gt 0 - do - case "$1" in - -u|--update) - update=1 - shift - ;; - *) - echo "$1 is not an accepted option..." - shift - ;; - esac - done - - # perform update if requested - if [ $update -ne 0 ] - then - # check for each template's existance - for f in "$PWD"/templates/*.ipynb - do - # get basename of notebook - name=$(basename "$f") - - # get difference between similarly named notebook in scripts folder - DIFF=$(diff "$f" "$PWD/scripts/$name" 2>/dev/null) - - # get exit-code of diff - # *-------------------------------------------------------------------* - # | 0 | successful and no differences. systematically univeral | - # | 1 | successful and differences. systematically universal | - # | -1 | error (no file, etc.). error code is system dependent | - # | 2 | error (no file, etc.). error code is system dependent | - # *-------------------------------------------------------------------* - DIFFEXIT=$? - - # check for error - if [ $DIFFEXIT -ne 0 ] && [ $DIFFEXIT -ne 1 ] - then - echo "$name was not found. Creating new file $name in scripts" - cp -- "$f" "$PWD/scripts/$name" - - # if difference, add updated file - elif [ "$DIFF" != "" ] - then - echo "WARNING: The file $name is being overwritten..." - cp -- "$f" "$PWD/scripts/$name" - fi - done - else - # -n for no overwrite - # this is to prevent the case of an empty scripts directory not being filled - cp -n "$PWD"/templates/*.ipynb "$PWD/scripts/." - fi -else - # since there is no scripts directory, just make one and copy from templates - mkdir "$PWD/scripts" - cp "$PWD"/templates/*.ipynb "$PWD/scripts/." -fi \ No newline at end of file