Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 58 additions & 82 deletions .github/workflows/package-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@ name: Package tests

on:
pull_request:
branches:
- main
- master
- dev
branches: [main, master, dev]
push:
branches-ignore:
- main
- master
- dev
branches-ignore: [main, master, dev]
workflow_call:

permissions:
Expand All @@ -33,26 +27,30 @@ jobs:
matrix: ${{ fromJson(needs.package-filter.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Poetry
uses: abatilo/actions-poetry@v2
- name: Install pre-commit
run: |
pip install pre-commit
- name: Run pre-commit hooks and check for changes

- uses: astral-sh/setup-uv@v4

- name: Install and run pre-commit
run: |
cd "${{ matrix.package_dir }}"
uv sync --all-extras
uv run pre-commit run \
--from-ref ${{ github.event.pull_request.base.sha || github.event.before }} \
--to-ref ${{ github.event.pull_request.head.sha || github.sha }}

poetry run pre-commit run --files ./**/**
if [[ $(git status --porcelain) ]]
then
echo "::error::pre-commit hooks failed for ${{ matrix.package_name }}" && exit 1
fi
- name: Show dirty files (if pre-commit failed)
if: failure()
run: |
cd "${{ matrix.package_dir }}"
git status --porcelain
git diff

docker:
name: Docker | Build ${{ matrix.package_name }}
Expand All @@ -63,92 +61,70 @@ jobs:
matrix: ${{ fromJson(needs.package-filter.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check if Dockerfile exists
id: check_dockerfile
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- name: Docker | Build Image
run: |
if [ -f "${{ matrix.package_dir }}/Dockerfile" ]; then
echo "Dockerfile exists"
echo "dockerfile_exists=true" >> $GITHUB_ENV
else
echo "Dockerfile does not exist"
echo "dockerfile_exists=false" >> $GITHUB_ENV
if [ ! -f "${{ matrix.package_dir }}/Dockerfile" ]; then
echo "No Dockerfile found, skipping"
exit 0
fi
- name: Docker | Tag
id: docker_tag
if: env.dockerfile_exists == 'true'
run: |
version=$(cat ${{ matrix.package_dir }}/VERSION)
tag=polusai/${{ matrix.package_name }}:${version}
echo "tag will be ${tag}"
echo "tag=${tag}" >> $GITHUB_OUTPUT
- name: Docker | Setup Buildx
uses: docker/setup-buildx-action@v3
- name: Docker | Check if Image exists
if: env.dockerfile_exists == 'true'
run: |
tag=${{ steps.docker_tag.outputs.tag }}
docker pull ${tag} > /dev/null \
&& $(echo "::error::${tag} already exists on DockerHub" && exit 1) \
|| echo "success"
- name: Docker | Build Image
if: env.dockerfile_exists == 'true'
run: |
&& (echo "::error::${tag} already exists on DockerHub" && exit 1) \
|| echo "Image does not exist, safe to build"
cp .gitignore ${{ matrix.package_dir }}/.dockerignore
cd "${{ matrix.package_dir }}"
if [ -f "build-docker.sh" ]; then
bash build-docker.sh
else
docker build . -t ${{ steps.docker_tag.outputs.tag }}
docker build . -t ${tag}
fi
bash build-docker.sh
# docker buildx build --platform linux/amd64,linux/arm64 -t ${tag} --push .

tests:
name: Test | ${{ matrix.package_name }}
needs: package-filter
timeout-minutes: 30
if: ${{ needs.package-filter.outputs.num_packages > 0 }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.package-filter.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
lfs: true
- name: Set up Python
uses: actions/setup-python@v5

- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Conda

- name: Run tests with conda
if: ${{ hashFiles(format('{0}/environment.yml', matrix.package_dir)) != '' }}
uses: conda-incubator/setup-miniconda@v2

- name: Run tests with conda
if: ${{ hashFiles(format('{0}/environment.yml', matrix.package_dir)) != '' }}
shell: bash -l {0}
run: |
package_dir=${{ matrix.package_dir }}
cd $package_dir
if [ -f "environment.yml" ]; then
conda init bash
source ~/.bashrc
conda env create -f environment.yml
conda activate project_env
pip install -e ".[all]"
conda install pytest
python -X faulthandler -m pytest -v -p no:faulthandler
echo "conda_installed=true" >> $GITHUB_ENV
else
echo "conda_installed=false" >> $GITHUB_ENV
fi
- name: Install Poetry
uses: abatilo/actions-poetry@v2
- name: Run tests with poetry
if: env.conda_installed == 'false'
run: |
poetry config virtualenvs.create false
cd ${{ matrix.package_dir }}
conda env create -f environment.yml
conda activate project_env
pip install -e ".[all]"
conda install pytest -y
python -X faulthandler -m pytest -v -p no:faulthandler

package_dir=${{ matrix.package_dir }}
cd $package_dir
- name: Run tests with uv
if: ${{ hashFiles(format('{0}/environment.yml', matrix.package_dir)) == '' }}
uses: astral-sh/setup-uv@v4

poetry install
python -X faulthandler -m pytest -v -p no:faulthandler
- name: Run tests with uv
if: ${{ hashFiles(format('{0}/environment.yml', matrix.package_dir)) == '' }}
run: |
cd ${{ matrix.package_dir }}
uv sync --all-extras || uv pip install -e ".[all]"
uv pip install pytest
uv run python -X faulthandler -m pytest -v -p no:faulthandler
2 changes: 1 addition & 1 deletion features/nyxus-tool/.bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.8
current_version = 0.1.8-dev2
commit = True
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)(?P<dev>\d+))?
Expand Down
14 changes: 13 additions & 1 deletion features/nyxus-tool/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.8-dev2] - 2026-03-06
- Replace the nyxus `featurize_file` function with a simple `featurize` function as `single_roi` flag does not respect `ignore_mask_files`.
- Handling of additional tuning parameter for nyxus features.

## [0.1.8-dev1] - 2024-11-15
- Fix a bug for no objects in label images.
- Updated bfio base-container image.

## [0.1.8-dev0] - 2024-09-26
- Renamed nyxus-plugin to nyxus-tool.
- updated nyxus package and bfio base-container image.
- Updated nyxus package and bfio base-container image.
32 changes: 20 additions & 12 deletions features/nyxus-tool/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
FROM polusai/bfio:2.4.3
FROM polusai/bfio:2.5.0

# environment variables defined in polusai/bfio
ENV EXEC_DIR="/opt/executables"
ENV POLUS_IMG_EXT=".ome.tif"
ENV POLUS_TAB_EXT=".csv"
ENV POLUS_LOG="INFO"
ENV NUM_WORKERS=4

# Work directory defined in the base container
WORKDIR ${EXEC_DIR}

# TODO: Change the tool_dir to the tool directory
ENV TOOL_DIR="features/nyxus-tool"
# Copy the tool
COPY . ${EXEC_DIR}/nyxus-tool

# Copy the repository into the container
RUN mkdir image-tools
COPY . ${EXEC_DIR}/image-tools
# Upgrade pip/setuptools/wheel first
RUN python3 -m pip install --upgrade pip setuptools wheel

# Install the tool
RUN pip3 install "${EXEC_DIR}/image-tools/${TOOL_DIR}" --no-cache-dir
# Install build dependencies (compiler + Python headers)
RUN apt-get update && apt-get install -y \
build-essential \
python3.11-dev \
&& rm -rf /var/lib/apt/lists/*

# Set the entrypoint
# TODO: Change the entrypoint to the tool entrypoint
# Install your package (Annoy will build successfully)
RUN pip3 install "${EXEC_DIR}/nyxus-tool" --no-cache-dir

# Clean up build tools to reduce image size
RUN apt-get purge -y build-essential python3.11-dev \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Entrypoint
ENTRYPOINT ["python3", "-m", "polus.images.features.nyxus_tool"]
CMD ["--help"]
65 changes: 46 additions & 19 deletions features/nyxus-tool/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
# Nyxus-Plugin(v0.1.8)
# Nyxus-tool(v0.1.8-dev2)


Nyxus plugin uses parallel processing of [Nyxus python package](https://pypi.org/project/nyxus/) to extract nyxus features from intensity-label image data. Especially useful when processing high throughput screens.
Parallelized feature extraction from intensity + label image pairs using the **[Nyxus](https://pypi.org/project/nyxus/)** library.

Especially useful for high-throughput microscopy screens.

Contact [Hamdah Shafqat Abbasi](mailto: hamdah.abbasi@axleinfo.com) for more information.

For more information on WIPP, visit the [official WIPP page](https://isg.nist.gov/deepzoomweb/software/wipp).


## Note
Use two separate [filepatterns](https://filepattern.readthedocs.io/en/latest/) for intensity and label images.
For example if you have label images of one channel `c1`\
`segPattern='p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c1.ome.tif'`\
Use filepattern if you require to extract features from intensity images of all other channels\
`intPattern=p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c{c}.ome.tif`
## Important notes

- Use two separate **[filepattern](https://filepattern.readthedocs.io/en/latest/)** for intensity and label images.
- Example naming scheme:

Intensity (multi-channel):
`intPattern=p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c{c}.ome.tif`

## Output Format
Computed features outputs can be saved in either of formats `.csv`, `.arrow`, `.parquet` by passing values `pandas`, `arrowipc`, `parquet` to `fileExtension`. By default plugin saves outputs in `.csv`
Segmentation :
`segPattern='p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c1.ome.tif'`

- `--singleRoi` mode treats each intensity image as one whole-object ROI (ignores segmentation mask)
- Nyxus parameters (e.g., `neighbor_distance`, `pixels_per_micron`) are passed via repeatable `--kwargs KEY=VALUE`
- Output file extension (format) is controlled via environment variable `POLUS_TAB_EXT` (default: `pandas`; options: `pandas`, `arrowipc`, `parquet`)


## Building
Expand All @@ -29,20 +36,40 @@ To build the Docker image for the conversion plugin, run
If WIPP is running, navigate to the plugins page and add a new plugin. Paste the
contents of `plugin.json` into the pop-up window and submit.

## Quick run example (Docker)

See `run-plugin.sh` for a template.


```bash
docker run --rm -v /path/to/data:/data \
-e POLUS_TAB_EXT=pandas \
polusai/nyxus-tool:0.1.8-dev2 \
--inpDir /data/intensity \
--segDir /data/segmentation \
--intPattern 'p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c{c}.ome.tif' \
--segPattern 'p00{z}_x{x+}_y{y+}_wx{t}_wy{p}_c1.ome.tif' \
--features "BASIC_MORPHOLOGY,ALL_INTENSITY" \
--kwargs neighbor_distance=5 \
--kwargs pixels_per_micron=1.0 \
--singleRoi false \
--outDir /data/features
```


## Options

This plugin takes nine input arguments and one output argument:
This plugin takes seven input arguments and one output argument:

| Name | Description | I/O | Type |
|--------------------|--------------------------------------------------------------------|--------|---------------|
| `--inpDir` | Input image directory | Input | collection |
| `--segDir` | Input label image directory | Input | collection |
| `--inpDir` | Intensity images folder | Input | collection |
| `--segDir` | Label / segmentation images folder directory | Input | collection |
| `--intPattern` | Filepattern to parse intensity images | Input | string |
| `--segPattern` | Filepattern to parse label images | Input | string |
| `--features` | [nyxus features](https://pypi.org/project/nyxus/) | Input | string |
| `--fileExtension` | A desired file format for nyxus features output | Input | enum |
| `--neighborDist` | Distance between two neighbor objects | Input | integer |
| `--pixelPerMicron` | Pixel Size in micrometer | Input | float |
| `--singleRoi` | Treat intensity image as single roi and ignoring segmentation mask | Input | bool |
| `--features` | [Feature groups or individual nyxus features (comma-separated or repeated)](https://pypi.org/project/nyxus/) | Input | string | |
| `--singleRoi` | Treat each intensity image as single ROI (whole-image features, no mask) | Input | bool
| `--kwargs` | Nyxus params as KEY=VALUE (repeatable; e.g., neighbor_distance=5) | Input | list[str] |
| `--outDir` | Output collection | Output | collection |
| `--preview` | Generate a JSON file with outputs | Output | JSON |
| `--preview` | Generate a JSON file with outputs | Output |
JSON |
2 changes: 1 addition & 1 deletion features/nyxus-tool/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.8
0.1.8-dev2
Loading
Loading