diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d261165..bf7dca0 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -7,10 +7,9 @@ name: Docker on: pull_request: - branches: [ "main" ] - + branches: ["main"] release: - types: [ published ] + types: [published] env: # Use docker.io for Docker Hub if empty @@ -18,10 +17,8 @@ env: # github.repository as / IMAGE_NAME: ${{ github.repository }} - jobs: build: - runs-on: ubuntu-latest permissions: contents: read @@ -32,12 +29,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Enable QEMU for cross-platform builds # https://github.com/docker/setup-qemu-action - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 # Install the cosign tool except on PR # https://github.com/sigstore/cosign-installer @@ -45,7 +42,7 @@ jobs: if: github.event_name != 'pull_request' uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 with: - cosign-release: 'v2.2.4' + cosign-release: "v2.2.4" # Set up BuildKit Docker container builder to be able to build # multi-platform images and export cache diff --git a/.github/workflows/python-build-package.yml b/.github/workflows/python-build-package.yml new file mode 100644 index 0000000..bdda51b --- /dev/null +++ b/.github/workflows/python-build-package.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Test Python Build + +on: + pull_request: + branches: ["main"] + release: + types: [published] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + python -m pip install . --no-cache-dir + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 3b0cb0e..4c12bcf 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,9 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: "3.x" diff --git a/README.md b/README.md index ff089d4..2ba16a8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # Backsub - pixel-by-pixel channel subtraction tool for multiplexed immunofluorescence data +[![PyPI](https://img.shields.io/pypi/v/backsub?style=flat-square)](https://pypi.org/project/backsub/) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/backsub?style=flat-square)](https://pypi.org/project/backsub/) +[![PyPI - License](https://img.shields.io/pypi/l/backsub?style=flat-square)](https://pypi.org/project/backsub/) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/backsub?style=flat-square)](https://pypi.org/project/backsub/) Backsub performs pixel-by-pixel background subtraction between marker and background channels scaled by their respective exposure times. The outputs are saved as pyramidal OME-TIFF files. It was originally developed for data produced by the Lunaphore COMET platform and is fully compatible with the [MCMICRO](https://mcmicro.org) pipeline. @@ -23,7 +27,8 @@ Marker_corrected = Marker_raw − Background × (Exposure_Marker / Exposure_Back ## Installation -Backsub can be installed directly from PyPI, or run in a preconfigured Docker container. For development or container builds, a fixed-version Conda environment is provided. +Backsub can be installed directly from PyPI, or run in a preconfigured Docker container. For development or container builds, a fixed-version Conda environment is provided. +Backsub requires Python >=3.11 ### Option 1: Install from PyPI @@ -111,7 +116,7 @@ An exemplary [markers.csv](https://github.com/SchapiroLabor/Background_subtracti | -mo | --marker-output | File path where the output marker (CSV) file matching the output image will be saved. | string, ends with `.csv` | | yes | | -mpp | --pixel-size | Pixel size provided in microns (microns per pixel). If not provided, image metadata will be checked. If that is not successful, the value will be set to 1. | float | None | no | | -sr | --save-ram | Optional flag to approximately cut RAM usage in half. Note that the dimensions of the reduced resolution levels (sub-levels) of the output pyramidal image will slightly differ whether or not the argument is used. | boolean flag | false | no | -| -comp | --compression | The output pyramidal OME-TIFF will be compressed using the specified compression. Set to "none" for no compression. | string, either "lzw", "zlib", or "none" | "lzw" | no | +| -comp | --compression | The output pyramidal OME-TIFF will be compressed using the specified compression. Set to "none" for no compression. | string, either "lzw", "zlib", or "none" | "zlib" | no | | -ts | --tile-size | Tile size used for writing pyramidal outputs. Note that the file size is smaller for smaller tile size values. | integer, multiple of 16 | 256 | no | | -dsf | --downscale-factor | Downscale factor for pyramid layer creation. This value will only be used if the input image is NOT pyramidal. If the input image is pyramidal, the number of levels in the output image will be the same as in the input so the downscale factor won't be applied. | integer, larger than 1 | 2 | no | | -v | --version | Prints Backsub version. | | | | diff --git a/backsub/CLI.py b/backsub/CLI.py index e3588e2..4f1e02a 100644 --- a/backsub/CLI.py +++ b/backsub/CLI.py @@ -7,9 +7,9 @@ def get_args(_version): # Script description description = """ - Subtracts background from an image (signal) - acquired with fluorescence microscopy. - Subtraction is carried out via the formula (SignalImage-factor*BackgroundImage), + Subtracts background from an image (signal) + acquired with fluorescence microscopy. + Subtraction is carried out via the formula (SignalImage-factor*BackgroundImage), where factor is the ratio between exposure times of both images. """ @@ -23,14 +23,14 @@ def get_args(_version): inputs.add_argument( "-r", ## deprecation warning can be added later - "--root", ## deprecation warning can be added later + "--root", "-in", "--input", dest="input", action="store", type=pathlib.Path, required=True, - help="File path to input image file.", + help="File path to the input image file.", ) inputs.add_argument( @@ -41,8 +41,7 @@ def get_args(_version): type=pathlib.Path, required=True, help="""File path to the markers.csv file containing the list of marker names - and their respective background channels. - """, + and their respective background channels.""", ) inputs.add_argument( @@ -54,7 +53,9 @@ def get_args(_version): type=float, default=None, action="store", - help="pixel size in microns, i.e. microns per pixel(mpp)", + help="""Pixel size in microns, i.e. microns per pixel(mpp). If not provided, + the pixel size will be obtained from the image metadata. If that's + not successful, the value will be set to 1. [Default: None]""", ) inputs.add_argument( @@ -64,8 +65,7 @@ def get_args(_version): required=False, type=int, default=256, - help="""Tile size for image pyramid creation. Has to be a multiple of 16. - """, + help="""Tile size for image pyramid creation. Has to be a multiple of 16. [Default: 256]""", ) inputs.add_argument( @@ -76,20 +76,18 @@ def get_args(_version): type=int, default=2, help="""Downscale factor for the image pyramid. - This value will be only used if the input image is NOT pyramidal. - If input image is pyramidal, the number of levels in the output image - will be the same as in the input so the downscale factor is not applied. - """, + This value will be only used if the input image is NOT pyramidal. + If input image is pyramidal, the number of levels in the output image + will be the same as in the input so the downscale factor is not applied. [Default: 2]""", ) inputs.add_argument( "-sr", "--save-ram", action="store_true", - help="""RAM usage is cut in half when using this flag. - Notice that the dimensions of the reduced resolution levels (sub-levels) of - the output pyramidal image will slightly differ whether or not using this argument. - """, + help="""RAM usage is reduced when using this flag. + Notice that the dimensions of the reduced resolution levels (sub-levels) of + the output pyramidal image will slightly differ whether or not using this argument. [Default: False]""", ) inputs.add_argument( @@ -98,11 +96,10 @@ def get_args(_version): dest="compression", required=False, type=str, - default="lzw", + default="zlib", choices=["lzw", "none", "deflate", "zlib"], help="""If set, the output pyramidal image will be compressed using the specified - compression method. Set to "none" for no compression. Default is LZW. An alternative is zlib. - """, + compression method. Set to "none" for no compression. [Default: zlib]""", ) # VERSION CONTROL @@ -118,7 +115,7 @@ def get_args(_version): action="store", type=pathlib.Path, required=True, - help="File path where the output pyramidal OME-TIFF will be saved.", + help="File path where the output background-subtracted pyramidal OME-TIFF will be saved.", ) outputs.add_argument( diff --git a/environment.yml b/environment.yml index 64b8199..e8d1d46 100644 --- a/environment.yml +++ b/environment.yml @@ -1,7 +1,7 @@ name: backsub_env channels: - conda-forge - - defaults + - nodefaults dependencies: - python=3.12.11 - pandas=2.3.1 @@ -13,4 +13,6 @@ dependencies: - loguru=0.7.3 - dask-image=2024.5.3 - zarr=3.1.1 - - pip \ No newline at end of file + - pip + - psutil=7.2.2 + - imagecodecs=2026.3.6 diff --git a/pyproject.toml b/pyproject.toml index 1d17be8..d488123 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,26 +3,34 @@ name = "backsub" dynamic = ["version"] description = "Channel subtraction scaled by exposure times" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.11" license = { file = "LICENSE" } authors = [ { name="Krešimir Bestak", email="kbestak@gmail.com" } ] keywords = ["autofluorescence", "imaging", "immunofluorescence", "subtraction"] classifiers = [ + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License" ] dependencies = [ - "pandas", - "tifffile", - "ome_types", - "numpy", - "loguru", "dask", "dask-image", + "imagecodecs", + "loguru", + "numpy", + "ome_types", + "pandas", + "psutil", "scikit-image", + "tifffile", "zarr>=3" ] [project.urls]