Compare commits
46
Commits
e55152c55d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
645c75535c | ||
|
|
4a5a538bf3 | ||
|
|
564e59cd9f | ||
|
|
fcd9aa4e5d | ||
|
|
1809ec859f | ||
|
|
6da2cb5636 | ||
|
|
6fc6b6dfc3 | ||
|
|
1574bad208 | ||
|
|
651e6dd25a | ||
|
|
9871a98946 | ||
|
|
4fbf5b23e2 | ||
|
|
9416e774ad | ||
|
|
60b4e0fe7d | ||
|
|
825bc99523 | ||
|
|
05ed9112d8 | ||
|
|
8338af454f | ||
|
|
546e022cfa | ||
|
|
d8696bdb04 | ||
|
|
aadf235ca5 | ||
|
|
120453582c | ||
|
|
592e9a944b | ||
|
|
aff6dfb51b | ||
|
|
1020df649a | ||
|
|
d4a5bca8ba | ||
|
|
f20f82889d | ||
|
|
b049939214 | ||
|
|
d0c06256f1 | ||
|
|
3c787e8d29 | ||
|
|
3f8d1311aa | ||
|
|
a400fbd800 | ||
|
|
c5d77fe289 | ||
|
|
2d13851a38 | ||
|
|
7db52bb63b | ||
|
|
d19a8d2d69 | ||
|
|
a055e08a20 | ||
|
|
5f70c4152b | ||
|
|
02cfa39677 | ||
|
|
3c7af5da4d | ||
|
|
8a4c965477 | ||
|
|
22e8ab1dd2 | ||
|
|
3e33fe18c1 | ||
|
|
f0404d0419 | ||
|
|
e1caa1c0b0 | ||
|
|
453237cae9 | ||
|
|
4815690621 | ||
|
|
7f07f82fdb |
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
name: Publish
|
name: Publish_rs
|
||||||
|
|
||||||
on: [ workflow_dispatch ]
|
on: [ workflow_dispatch ]
|
||||||
|
|
||||||
@@ -10,23 +10,27 @@ jobs:
|
|||||||
name: Publish (crates.io)
|
name: Publish (crates.io)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
|
env:
|
||||||
|
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: ${{ env.CARGO_HOME }}
|
||||||
~/.cargo
|
|
||||||
key: cache-ubuntu-cargo-publish
|
key: cache-ubuntu-cargo-publish
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
export PATH="$CARGO_HOME/bin:$PATH"
|
||||||
if ! command -v rustc >/dev/null 2>&1; then
|
if ! command -v rustc >/dev/null 2>&1; then
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
else
|
else
|
||||||
rustup update
|
rustup update
|
||||||
fi
|
fi
|
||||||
|
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
|
||||||
cargo install cargo-release
|
cargo install cargo-release
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@@ -46,9 +50,9 @@ jobs:
|
|||||||
# which is reasonable, since this job only runs after the Linux, Windows, and WASM builds
|
# which is reasonable, since this job only runs after the Linux, Windows, and WASM builds
|
||||||
# have passed.
|
# have passed.
|
||||||
- name: "cargo release publish"
|
- name: "cargo release publish"
|
||||||
|
env:
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$HOME/.osxcross/bin:$PATH"
|
|
||||||
cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
|
|
||||||
cargo release \
|
cargo release \
|
||||||
publish \
|
publish \
|
||||||
--workspace \
|
--workspace \
|
||||||
@@ -61,6 +65,5 @@ jobs:
|
|||||||
- name: Store cache
|
- name: Store cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: ${{ env.CARGO_HOME }}
|
||||||
~/.cargo
|
|
||||||
key: cache-ubuntu-cargo-publish
|
key: cache-ubuntu-cargo-publish
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CI
|
name: Publish_py
|
||||||
|
|
||||||
on: [ workflow_dispatch ]
|
on: [ workflow_dispatch ]
|
||||||
|
|
||||||
@@ -9,8 +9,14 @@ jobs:
|
|||||||
pypi_publish:
|
pypi_publish:
|
||||||
name: Publish (pypi.org)
|
name: Publish (pypi.org)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||||
|
RUSTUP_HOME: ${{ github.workspace }}/.rustup
|
||||||
|
RUSTUP_TOOLCHAIN: stable
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -23,33 +29,62 @@ jobs:
|
|||||||
~/.cache/pip-wheel
|
~/.cache/pip-wheel
|
||||||
~/.cache/sccache
|
~/.cache/sccache
|
||||||
~/.cache/cargo-xwin
|
~/.cache/cargo-xwin
|
||||||
~/.cargo
|
${{ env.CARGO_HOME }}
|
||||||
~/.osxcross
|
~/.osxcross
|
||||||
key: cache-ubuntu-maturin-cross-compile
|
key: cache-ubuntu-maturin-cross-compile
|
||||||
|
|
||||||
- name: Install llvm
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
if ! command -v llvm-dlltool >/dev/null 2>&1; then
|
# the stock clang on noble (18) is too old for the 2025+ MSVC STL that
|
||||||
|
# cargo-xwin downloads (yvals_core.h: "error STL1000: expected Clang
|
||||||
|
# 19.0.0 or newer"), so take a newer clang toolchain from apt.llvm.org.
|
||||||
|
# apt.llvm.org sometimes half-publishes a new version, in which case the
|
||||||
|
# unversioned meta packages become uninstallable ("clang: Depends:
|
||||||
|
# clang-NN ... not installable"); so pick the newest versioned set that
|
||||||
|
# actually resolves (a dry-run checks the whole dependency closure) -
|
||||||
|
# nothing is hardcoded, this tracks the newest working release.
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y llvm
|
echo "deb [trusted=yes] https://apt.llvm.org/noble/ llvm-toolchain-noble main" | sudo tee /etc/apt/sources.list.d/llvm-toolchain-noble.list >/dev/null
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y build-essential cmake
|
||||||
|
NEWEST=""
|
||||||
|
for name in $(apt-cache search '^clang-[0-9]+$' | awk '{print $1}' | sort -rV); do
|
||||||
|
v=${name#clang-}
|
||||||
|
if sudo apt-get install --dry-run -y "clang-$v" "llvm-$v" "lld-$v" "libclang-$v-dev" >/dev/null 2>&1; then
|
||||||
|
NEWEST=$v
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
[ -n "$NEWEST" ] || { echo "no installable clang toolchain found in apt.llvm.org" >&2; exit 1; }
|
||||||
|
echo "using clang/llvm toolchain $NEWEST"
|
||||||
|
sudo apt install -y "clang-$NEWEST" "llvm-$NEWEST" "lld-$NEWEST" "libclang-$NEWEST-dev"
|
||||||
|
# expose the toolchain's own bin/lib dirs (clang, clang-cl, lld-link,
|
||||||
|
# llvm-lib, libclang) to the later steps
|
||||||
|
echo "LLVM_BIN=/usr/lib/llvm-$NEWEST/bin" >> "$GITHUB_ENV"
|
||||||
|
echo "LIBCLANG_PATH=/usr/lib/llvm-$NEWEST/lib" >> "$GITHUB_ENV"
|
||||||
|
export PATH="/usr/lib/llvm-$NEWEST/bin:$PATH"
|
||||||
|
clang --version | head -1
|
||||||
|
command -v clang clang-cl lld-link llvm-lib
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
export PATH="$CARGO_HOME/bin:$PATH"
|
||||||
if ! command -v rustc >/dev/null 2>&1; then
|
if ! command -v rustup >/dev/null 2>&1; then
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
else
|
|
||||||
rustup update
|
|
||||||
fi
|
fi
|
||||||
|
rustup toolchain install stable --profile minimal
|
||||||
|
rustup default stable
|
||||||
|
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
|
||||||
|
cargo --version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install sccache and maturin
|
- name: Install sccache and maturin
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
python -m pip install --upgrade pip maturin ziglang cargo-zigbuild
|
||||||
python -m pip install --upgrade pip
|
# in case an older copy was cargo-installed into the CARGO_HOME cache,
|
||||||
pip install maturin ziglang
|
# prevent it from shadowing the pip-installed one
|
||||||
|
rm -f "${CARGO_HOME:?}/bin/cargo-zigbuild" || true
|
||||||
if ! command -v sccache >/dev/null 2>&1; then
|
if ! command -v sccache >/dev/null 2>&1; then
|
||||||
cargo install sccache || pip install sccache
|
cargo install sccache || pip install sccache
|
||||||
fi
|
fi
|
||||||
@@ -57,7 +92,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Install xwin
|
- name: Install xwin
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
|
||||||
if ! command -v cargo-xwin >/dev/null 2>&1; then
|
if ! command -v cargo-xwin >/dev/null 2>&1; then
|
||||||
cargo install cargo-xwin || pip install cargo-xwin
|
cargo install cargo-xwin || pip install cargo-xwin
|
||||||
cargo xwin cache xwin
|
cargo xwin cache xwin
|
||||||
@@ -67,32 +101,87 @@ jobs:
|
|||||||
- name: Install osxcross
|
- name: Install osxcross
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.osxcross/bin:$PATH"
|
export PATH="$HOME/.osxcross/bin:$PATH"
|
||||||
if ! command -v osxcross >/dev/null 2>&1; then
|
if [ ! -d "$HOME/.osxcross/bin" ]; then
|
||||||
wget ${{ secrets.OSXCROSS_LINK }} -O osxcross.tar.gz
|
wget ${{ secrets.OSXCROSS_LINK }} -O osxcross.tar.gz
|
||||||
tar -xzf osxcross.tar.gz -C ~/
|
tar -xzf osxcross.tar.gz -C ~/
|
||||||
mv ~/osxcross ~/.osxcross
|
mv ~/osxcross ~/.osxcross
|
||||||
fi
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Shim clang for ring's aarch64-windows build
|
||||||
|
run: |
|
||||||
|
# ring 0.17.14 forces a plain `clang` for its C build on
|
||||||
|
# aarch64-pc-windows-msvc (its "must use Clang on Win AArch64"
|
||||||
|
# workaround), while the CFLAGS injected by cargo-xwin carry
|
||||||
|
# clang-cl-style `/imsvc <dir>` pairs that plain clang rejects.
|
||||||
|
# This shim rewrites them to `-I <dir>` and execs the real clang
|
||||||
|
# found further down PATH; all other invocations pass through
|
||||||
|
# untouched, and tools with other names (clang-cl, lld-link) are not
|
||||||
|
# affected.
|
||||||
|
mkdir -p /root/ci-shims
|
||||||
|
cat > /root/ci-shims/clang <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
args=()
|
||||||
|
prev_imsvc=0
|
||||||
|
for a in "$@"; do
|
||||||
|
if [ "$prev_imsvc" = "1" ]; then
|
||||||
|
args+=("-I$a")
|
||||||
|
prev_imsvc=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$a" in
|
||||||
|
/imsvc) prev_imsvc=1 ;;
|
||||||
|
/imsvc:*) args+=("-I${a#/imsvc:}") ;;
|
||||||
|
*) args+=("$a") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
self_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
real=""
|
||||||
|
IFS=":" read -r -a _dirs <<< "$PATH"
|
||||||
|
IFS=""
|
||||||
|
for d in "${_dirs[@]}"; do
|
||||||
|
[ "$d" = "$self_dir" ] && continue
|
||||||
|
if [ -x "$d/clang" ]; then real="$d/clang"; break; fi
|
||||||
|
done
|
||||||
|
[ -n "$real" ] || real=/usr/bin/clang
|
||||||
|
exec "$real" "${args[@]}"
|
||||||
|
EOF
|
||||||
|
chmod +x /root/ci-shims/clang
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.cargo/bin:$HOME/.osxcross/bin:$PATH"
|
export PATH="$HOME/.osxcross/bin:$PATH"
|
||||||
maturin sdist --out dist
|
# LLVM_BIN and LIBCLANG_PATH were set by the "Install build
|
||||||
rustup default nightly
|
# dependencies" step; put the shim (for ring's forced plain `clang`)
|
||||||
|
# in front of the toolchain so clang-cl / lld-link (distinct names)
|
||||||
|
# still resolve to the real toolchain
|
||||||
|
export PATH="/root/ci-shims:$LLVM_BIN:$PATH"
|
||||||
|
rm -rf dist
|
||||||
|
maturin sdist --out dist -m ndbioimage/Cargo.toml
|
||||||
|
|
||||||
rustup target add x86_64-unknown-linux-gnu --toolchain nightly
|
rustup target add x86_64-apple-darwin
|
||||||
maturin build --release --out dist --target x86_64-unknown-linux-gnu
|
maturin build --release --out dist --target x86_64-apple-darwin --zig --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
rustup target add aarch64-unknown-linux-gnu --toolchain nightly
|
rustup target add aarch64-apple-darwin
|
||||||
maturin build --release --out dist --target aarch64-unknown-linux-gnu --zig
|
maturin build --release --out dist --target aarch64-apple-darwin --zig --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
|
|
||||||
rustup target add x86_64-pc-windows-msvc --toolchain nightly
|
rustup target add x86_64-unknown-linux-gnu
|
||||||
maturin build --release --out dist --target x86_64-pc-windows-msvc
|
# the x86_64-gnu build links the runner's host liblzma (lzma-sys),
|
||||||
rustup target add aarch64-pc-windows-msvc --toolchain nightly
|
# which needs glibc symbols up to 2.34 - higher than the
|
||||||
maturin build --release --out dist --target aarch64-pc-windows-msvc
|
# manylinux_2_17 baseline
|
||||||
|
maturin build --release --out dist --target x86_64-unknown-linux-gnu --zig --auditwheel repair --compatibility manylinux_2_34 -m ndbioimage/Cargo.toml
|
||||||
|
rustup target add aarch64-unknown-linux-gnu
|
||||||
|
maturin build --release --out dist --target aarch64-unknown-linux-gnu --zig --auditwheel repair --compatibility manylinux_2_34 -m ndbioimage/Cargo.toml
|
||||||
|
rustup target add x86_64-unknown-linux-musl
|
||||||
|
maturin build --release --out dist --target x86_64-unknown-linux-musl --zig --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
|
rustup target add aarch64-unknown-linux-musl
|
||||||
|
maturin build --release --out dist --target aarch64-unknown-linux-musl --zig --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
|
|
||||||
rustup target add x86_64-apple-darwin --toolchain nightly
|
rustup target add x86_64-pc-windows-msvc
|
||||||
maturin build --release --out dist --target x86_64-apple-darwin --zig
|
maturin build --release --out dist --target x86_64-pc-windows-msvc --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
rustup target add aarch64-apple-darwin --toolchain nightly
|
rustup target add aarch64-pc-windows-msvc
|
||||||
maturin build --release --out dist --target aarch64-apple-darwin --zig
|
maturin build --release --out dist --target aarch64-pc-windows-msvc --auditwheel repair -m ndbioimage/Cargo.toml
|
||||||
|
|
||||||
- name: Store cache
|
- name: Store cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
@@ -102,7 +191,7 @@ jobs:
|
|||||||
~/.cache/pip-wheel
|
~/.cache/pip-wheel
|
||||||
~/.cache/sccache
|
~/.cache/sccache
|
||||||
~/.cache/cargo-xwin
|
~/.cache/cargo-xwin
|
||||||
~/.cargo
|
${{ env.CARGO_HOME }}
|
||||||
~/.osxcross
|
~/.osxcross
|
||||||
key: cache-ubuntu-maturin-cross-compile
|
key: cache-ubuntu-maturin-cross-compile
|
||||||
|
|
||||||
@@ -110,5 +199,6 @@ jobs:
|
|||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
skip-existing: true
|
||||||
env:
|
env:
|
||||||
GITHUB_WORKFLOW_REF: 1.10.1
|
GITHUB_WORKFLOW_REF: 1.10.1
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: pytest
|
||||||
|
|
||||||
|
on: [ workflow_dispatch, workflow_call, push, pull_request ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pypi_publish:
|
||||||
|
name: Publish (pypi.org)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||||
|
RUSTUP_HOME: ${{ github.workspace }}/.rustup
|
||||||
|
RUSTUP_TOOLCHAIN: stable
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
|
||||||
|
- name: Restore cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pip
|
||||||
|
~/.cache/pip-wheel
|
||||||
|
~/.cache/sccache
|
||||||
|
${{ env.CARGO_HOME }}
|
||||||
|
key: cache-ubuntu-maturin-cross-compile
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y llvm cmake build-essential libclang-dev clang
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
run: |
|
||||||
|
export PATH="$CARGO_HOME/bin:$PATH"
|
||||||
|
if ! command -v rustup >/dev/null 2>&1; then
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
fi
|
||||||
|
rustup toolchain install stable --profile minimal
|
||||||
|
rustup default stable
|
||||||
|
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
|
||||||
|
cargo --version
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install sccache and maturin
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip maturin ziglang cargo-zigbuild
|
||||||
|
# in case an older copy was cargo-installed into the CARGO_HOME cache,
|
||||||
|
# prevent it from shadowing the pip-installed one
|
||||||
|
rm -f "${CARGO_HOME:?}/bin/cargo-zigbuild" || true
|
||||||
|
if ! command -v sccache >/dev/null 2>&1; then
|
||||||
|
cargo install sccache || pip install sccache
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: PyTest
|
||||||
|
run: |
|
||||||
|
cd ndbioimage
|
||||||
|
python -m pip install .[test]
|
||||||
|
python -m pytest
|
||||||
|
|
||||||
|
- name: Store cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pip
|
||||||
|
~/.cache/pip-wheel
|
||||||
|
~/.cache/sccache
|
||||||
|
~/.cache/cargo-xwin
|
||||||
|
${{ env.CARGO_HOME }}
|
||||||
|
~/.osxcross
|
||||||
|
key: cache-ubuntu-maturin-cross-compile
|
||||||
+2
-1
@@ -29,6 +29,7 @@ venv/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
.cargo
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
@@ -72,11 +73,11 @@ docs/_build/
|
|||||||
# Pyenv
|
# Pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
/ndbioimage/tests/files/*
|
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
.agentbridge
|
.agentbridge
|
||||||
.agent-work
|
.agent-work
|
||||||
|
|
||||||
|
/ndbioimage/tests/files/*
|
||||||
/ndbioimage/py/ndbioimage/jassets
|
/ndbioimage/py/ndbioimage/jassets
|
||||||
/ndbioimage/py/ndbioimage/deps
|
/ndbioimage/py/ndbioimage/deps
|
||||||
/build_wheels.sh
|
/build_wheels.sh
|
||||||
+6
-10
@@ -3,28 +3,24 @@ resolver = "3"
|
|||||||
members = ["ndbioimage", "image-tiff", "image-registration"]
|
members = ["ndbioimage", "image-tiff", "image-registration"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.3.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
# the workspace must satisfy every member, so this is the highest `rust-version` of the members
|
rust-version = "1.94.1"
|
||||||
rust-version = "1.94.0"
|
|
||||||
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Read bio image formats using the bio-formats java package."
|
description = "Read bio image formats using the bio-formats java package."
|
||||||
homepage = "https://git.pomppervova.nl/wim/ndbioimage/src/branch/rs"
|
repository = "https://git.pomppervova.nl/wim/ndbioimage"
|
||||||
repository = "https://git.pomppervova.nl/wim/ndbioimage/src/branch/rs"
|
|
||||||
documentation = "https://docs.rs/ndbioimage"
|
documentation = "https://docs.rs/ndbioimage"
|
||||||
# inherited paths are resolved relative to the workspace root, not the member, so this has to point
|
readme = "README.md"
|
||||||
# at the member's readme
|
|
||||||
readme = "ndbioimage/README.md"
|
|
||||||
keywords = ["bioformats", "imread", "ndarray", "metadata"]
|
keywords = ["bioformats", "imread", "ndarray", "metadata"]
|
||||||
categories = ["multimedia::images", "science"]
|
categories = ["multimedia::images", "science"]
|
||||||
|
|
||||||
# profiles are only honoured in the workspace root manifest
|
|
||||||
[profile.test]
|
[profile.test]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
#debug = true
|
||||||
|
|
||||||
[profile.dev.package.backtrace]
|
[profile.dev.package.backtrace]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# ndbioimage
|
[](https://git.pomppervova.nl/wim/ndbioimage/actions?workflow=pytest.yml)
|
||||||
|
|
||||||
[](https://github.com/pomppervova/ndbioimage/actions/workflows/pytest.yml)
|
|
||||||
|
|
||||||
|
# NDBioImage
|
||||||
## Work in progress
|
## Work in progress
|
||||||
|
|
||||||
Rust rewrite of python version. Read bio image formats using the bio-formats java package.
|
Rust rewrite of python version. Read bio image formats using some Rust code, the libczi api, and if needed the
|
||||||
[https://www.openmicroscopy.org/bio-formats/](https://www.openmicroscopy.org/bio-formats/)
|
bio-formats java package.
|
||||||
|
|
||||||
Exposes (bio) images as a numpy ndarray-like object (Python) or a struct that can be sliced like an ndarray Array
|
Exposes (bio) images as a numpy ndarray-like object (Python) or a struct that can be sliced like an ndarray Array
|
||||||
(Rust), but without loading the whole image into memory, reading from the file only when needed. Some metadata is read
|
(Rust), but without loading the whole image into memory, reading from the file only when needed. Some metadata is read
|
||||||
@@ -18,20 +17,6 @@ Currently, it supports imagej tif files, czi files, micromanager tif sequences a
|
|||||||
|
|
||||||
To transition to semver, versions before 0.1.0 were yanked from crates.io.
|
To transition to semver, versions before 0.1.0 were yanked from crates.io.
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install ndbioimage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installation with option to write mp4 or mkv:
|
|
||||||
|
|
||||||
Work in progress! Make sure ffmpeg is installed.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install ndbioimage[write]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Python
|
### Python
|
||||||
+1
-1
Submodule image-registration updated: 7eb39a2fbf...2b01f65cc9.
+1
-1
Submodule image-tiff updated: 96d37bb087...c462915a61.
@@ -6,12 +6,11 @@ rust-version.workspace = true
|
|||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
documentation.workspace = true
|
documentation.workspace = true
|
||||||
readme.workspace = true
|
|
||||||
keywords.workspace = true
|
keywords.workspace = true
|
||||||
categories.workspace = true
|
categories.workspace = true
|
||||||
|
readme = "../README.md"
|
||||||
exclude = ["/tests"]
|
exclude = ["/tests"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -29,15 +28,15 @@ image-registration = { path = "../image-registration", version = "0.1", optional
|
|||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
indicatif = { version = "0.18", features = ["rayon"], optional = true }
|
indicatif = { version = "0.18", features = ["rayon"], optional = true }
|
||||||
j4rs = { version = "0.25", optional = true }
|
j4rs = { version = "0.25", optional = true }
|
||||||
libczirw-sys = { version = "0.5", optional = true }
|
libczirw-sys = { version = "0.5.2", optional = true }
|
||||||
ndarray = { version = "0.17", features = ["serde"] }
|
ndarray = { version = "0.17", features = ["serde"] }
|
||||||
num = "0.4"
|
num = "0.4"
|
||||||
numpy = { version = "0.29", optional = true }
|
numpy = { version = "0.29", optional = true }
|
||||||
ome-metadata = "0.5"
|
ome-metadata = "0.6"
|
||||||
ordered-float = { version = "5", optional = true }
|
ordered-float = { version = "5", optional = true }
|
||||||
phf = { version = "0.14", features = ["macros"] }
|
phf = { version = "0.14", features = ["macros"] }
|
||||||
postcard = { version = "1", features = ["use-std"], optional = true }
|
postcard = { version = "1", features = ["use-std"], optional = true }
|
||||||
pyo3 = { version = "0.29", features = ["abi3-py310", "anyhow", "generate-import-lib"], optional = true }
|
pyo3 = { version = "0.29", features = ["abi3-py310", "anyhow", "generate-import-lib", "multiple-pymethods"], optional = true }
|
||||||
pyo3-stub-gen = { version = "0.23", optional = true }
|
pyo3-stub-gen = { version = "0.23", optional = true }
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
@@ -51,6 +50,9 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread"], optional = true }
|
|||||||
thread_local = { version = "1", optional = true }
|
thread_local = { version = "1", optional = true }
|
||||||
xmltree = { version = "0.12", optional = true }
|
xmltree = { version = "0.12", optional = true }
|
||||||
yaml_serde = { version = "0.10", optional = true }
|
yaml_serde = { version = "0.10", optional = true }
|
||||||
|
# "bioformats 0.1.8" is written for "zip 2.4.x", where zip::read::ZipFile has a single lifetime parameter.
|
||||||
|
# "zip >= 2.5" added a second "R: Read" generic (2.6.1 breaks bioformats), so the build fails.
|
||||||
|
zip = { version = "=2.4.2", optional = true, default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
@@ -69,7 +71,7 @@ all = ["bioformats_java", "bioformats_rust", "czi", "gpl-formats", "movie", "tif
|
|||||||
gpl-formats = []
|
gpl-formats = []
|
||||||
python = ["dep:pyo3", "dep:numpy", "dep:pyo3-stub-gen", "dep:postcard", "ome-metadata/python"]
|
python = ["dep:pyo3", "dep:numpy", "dep:pyo3-stub-gen", "dep:postcard", "ome-metadata/python"]
|
||||||
czi = ["dep:libczirw-sys", "dep:xmltree", "dep:thread_local"]
|
czi = ["dep:libczirw-sys", "dep:xmltree", "dep:thread_local"]
|
||||||
bioformats_rust = ["dep:bioformats", "dep:thread_local"]
|
bioformats_rust = ["dep:bioformats", "dep:thread_local", "dep:zip"]
|
||||||
bioformats_java = ["dep:j4rs", "dep:thread_local", "dep:downloader"]
|
bioformats_java = ["dep:j4rs", "dep:thread_local", "dep:downloader"]
|
||||||
tiffwrite = ["dep:tiffwrite", "dep:indicatif", "dep:console", "dep:rayon"]
|
tiffwrite = ["dep:tiffwrite", "dep:indicatif", "dep:console", "dep:rayon"]
|
||||||
tiffseq = ["dep:tiff", "dep:yaml_serde"]
|
tiffseq = ["dep:tiff", "dep:yaml_serde"]
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# ruff: noqa: E501, F401, F403, F405
|
# ruff: noqa: E501, F401, F403, F405
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
|
import collections
|
||||||
|
import collections.abc
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import typing
|
import typing
|
||||||
@@ -52,6 +54,8 @@ class Flags:
|
|||||||
def __new__(cls) -> Flags: ...
|
def __new__(cls) -> Flags: ...
|
||||||
def __getnewargs__(self) -> None: ...
|
def __getnewargs__(self) -> None: ...
|
||||||
def __getitem__(self, item: builtins.str) -> builtins.bool: ...
|
def __getitem__(self, item: builtins.str) -> builtins.bool: ...
|
||||||
|
def __str__(self) -> builtins.str: ...
|
||||||
|
def __repr__(self) -> builtins.str: ...
|
||||||
|
|
||||||
class Imread:
|
class Imread:
|
||||||
r"""
|
r"""
|
||||||
@@ -96,16 +100,6 @@ class Imread:
|
|||||||
the name of the reader used to open the file
|
the name of the reader used to open the file
|
||||||
"""
|
"""
|
||||||
@property
|
@property
|
||||||
def transforms(self) -> transforms.Transforms:
|
|
||||||
r"""
|
|
||||||
get the transformation
|
|
||||||
"""
|
|
||||||
@transforms.setter
|
|
||||||
def transforms(self, value: transforms.Transforms) -> None:
|
|
||||||
r"""
|
|
||||||
set the transformation
|
|
||||||
"""
|
|
||||||
@property
|
|
||||||
def path(self) -> pathlib.Path:
|
def path(self) -> pathlib.Path:
|
||||||
r"""
|
r"""
|
||||||
the file path
|
the file path
|
||||||
@@ -220,6 +214,38 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
the name of the tube lens
|
the name of the tube lens
|
||||||
"""
|
"""
|
||||||
|
@property
|
||||||
|
def channel_names(self) -> builtins.dict[builtins.int, builtins.str]:
|
||||||
|
r"""
|
||||||
|
names of the channels in order
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def summary(self) -> builtins.str:
|
||||||
|
r"""
|
||||||
|
gives a helpful summary of the recorded experiment
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def interpolation_order(self) -> typing.Optional[builtins.int]:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
@interpolation_order.setter
|
||||||
|
def interpolation_order(self, value: typing.Optional[builtins.int]) -> None:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def transforms(self) -> transforms.Transforms:
|
||||||
|
r"""
|
||||||
|
get the transformation
|
||||||
|
"""
|
||||||
|
@transforms.setter
|
||||||
|
def transforms(self, value: transforms.Transforms) -> None:
|
||||||
|
r"""
|
||||||
|
set the transformation
|
||||||
|
"""
|
||||||
def __new__(
|
def __new__(
|
||||||
cls,
|
cls,
|
||||||
path: str | pathlib.Path | Imread | bytes,
|
path: str | pathlib.Path | Imread | bytes,
|
||||||
@@ -236,6 +262,10 @@ class Imread:
|
|||||||
reader: use a specific reader to open the file,
|
reader: use a specific reader to open the file,
|
||||||
view available choices with Imread.available_readers
|
view available choices with Imread.available_readers
|
||||||
"""
|
"""
|
||||||
|
def debug(self) -> None:
|
||||||
|
r"""
|
||||||
|
some debug info
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def warm_jvm() -> None:
|
def warm_jvm() -> None:
|
||||||
r"""
|
r"""
|
||||||
@@ -247,7 +277,10 @@ class Imread:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def jvm_is_running() -> builtins.bool:
|
def jvm_is_running() -> builtins.bool:
|
||||||
r"""
|
r"""
|
||||||
returns whether the jvm was already created on this thread
|
returns whether the jvm was already created in this process
|
||||||
|
|
||||||
|
this is false in a process that inherited a jvm across a fork, since such a jvm cannot be
|
||||||
|
used; reading a bio-formats file there raises instead
|
||||||
"""
|
"""
|
||||||
def set_flags(
|
def set_flags(
|
||||||
self,
|
self,
|
||||||
@@ -270,47 +303,19 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
only remains for backwards compatibility
|
only remains for backwards compatibility
|
||||||
"""
|
"""
|
||||||
def reshape(self, order: builtins.str, copy: builtins.bool) -> typing.Any:
|
def reshape(
|
||||||
r"""
|
|
||||||
reshape the view with a new axis order
|
|
||||||
"""
|
|
||||||
def with_transform(
|
|
||||||
self,
|
self,
|
||||||
channels: builtins.bool = True,
|
*shape: typing.Any,
|
||||||
drift: builtins.bool = False,
|
order: builtins.str = "C",
|
||||||
file: typing.Optional[typing.Any] = None,
|
copy: typing.Optional[builtins.bool] = None,
|
||||||
bead_files: typing.Optional[typing.Any] = None,
|
) -> Imread | numpy.NDArray:
|
||||||
main_channel: typing.Optional[builtins.int] = None,
|
|
||||||
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
|
||||||
) -> Imread:
|
|
||||||
r"""
|
r"""
|
||||||
return a new view with transformations applied (channel alignment, drift correction)
|
reshape the view with a new axis order, arguments order and copy cannot be used and will
|
||||||
backward compatibility
|
raise an exception if order != "C" or copy is False
|
||||||
"""
|
"""
|
||||||
def with_transforms(
|
def squeeze(
|
||||||
self,
|
self, axes: str | collections.abc.Sequence[int | str] = None
|
||||||
channels: builtins.bool = True,
|
) -> Imread | float | int: ...
|
||||||
drift: builtins.bool = False,
|
|
||||||
file: typing.Optional[typing.Any] = None,
|
|
||||||
bead_files: typing.Optional[typing.Any] = None,
|
|
||||||
main_channel: typing.Optional[builtins.int] = None,
|
|
||||||
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
|
||||||
) -> Imread:
|
|
||||||
r"""
|
|
||||||
return a new view with transformations applied (channel alignment, drift correction)
|
|
||||||
"""
|
|
||||||
def load_transforms_from_yaml(
|
|
||||||
self, path: builtins.str | os.PathLike | pathlib.Path
|
|
||||||
) -> None: ...
|
|
||||||
def calculate_channel_transforms_2d(
|
|
||||||
self, main_channel: builtins.int
|
|
||||||
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
|
||||||
def calculate_channel_transforms_3d(
|
|
||||||
self, main_channel: builtins.int
|
|
||||||
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
|
||||||
def calculate_drift_transforms_2d(self) -> builtins.list[transforms.Transform]: ...
|
|
||||||
def calculate_drift_transforms_3d(self) -> builtins.list[transforms.Transform]: ...
|
|
||||||
def squeeze(self) -> numpy.ndarray | int | float: ...
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
r"""
|
r"""
|
||||||
close the file: does nothing as this is handled automatically
|
close the file: does nothing as this is handled automatically
|
||||||
@@ -323,7 +328,19 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
change the data type of the view: (u)int(8/16/32) or float(32/64)
|
change the data type of the view: (u)int(8/16/32) or float(32/64)
|
||||||
"""
|
"""
|
||||||
def __getitem__(self, n: typing.Any) -> typing.Any:
|
def __call__(
|
||||||
|
self,
|
||||||
|
c: typing.Optional[int | slice | numpy.ArrayLike] = None,
|
||||||
|
z: typing.Optional[int | slice | numpy.ArrayLike] = None,
|
||||||
|
t: typing.Optional[int | slice | numpy.ArrayLike] = None,
|
||||||
|
y: typing.Optional[int | slice | numpy.ArrayLike] = None,
|
||||||
|
x: typing.Optional[int | slice | numpy.ArrayLike] = None,
|
||||||
|
) -> typing.Optional[int | Imread]:
|
||||||
|
r"""
|
||||||
|
same as im[] but allowing keyword axes, but slices need to made with slice()
|
||||||
|
also, no ellipsis or new axes
|
||||||
|
"""
|
||||||
|
def __getitem__(self, n: typing.Any) -> typing.Optional[int | Imread]:
|
||||||
r"""
|
r"""
|
||||||
slice the view and return a new view or a single number
|
slice the view and return a new view or a single number
|
||||||
"""
|
"""
|
||||||
@@ -331,7 +348,7 @@ class Imread:
|
|||||||
self,
|
self,
|
||||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||||
copy: typing.Optional[builtins.bool] = None,
|
copy: typing.Optional[builtins.bool] = None,
|
||||||
) -> typing.Any:
|
) -> numpy.typing.NDArray:
|
||||||
r"""
|
r"""
|
||||||
convert to a numpy array, optionally with a different dtype
|
convert to a numpy array, optionally with a different dtype
|
||||||
"""
|
"""
|
||||||
@@ -339,139 +356,199 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
check if an item is contained in the view (not implemented)
|
check if an item is contained in the view (not implemented)
|
||||||
"""
|
"""
|
||||||
def __lt__(self, other: typing.Any) -> typing.Any:
|
def __lt__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise less than comparison
|
element-wise less than comparison
|
||||||
"""
|
"""
|
||||||
def __le__(self, other: typing.Any) -> typing.Any:
|
def __le__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise less than or equal comparison
|
element-wise less than or equal comparison
|
||||||
"""
|
"""
|
||||||
def __eq__(self, other: typing.Any) -> typing.Any:
|
def __eq__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise equality comparison
|
element-wise equality comparison
|
||||||
"""
|
"""
|
||||||
def __ne__(self, other: typing.Any) -> typing.Any:
|
def __ne__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise not equal comparison
|
element-wise not equal comparison
|
||||||
"""
|
"""
|
||||||
def __gt__(self, other: typing.Any) -> typing.Any:
|
def __gt__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise greater than comparison
|
element-wise greater than comparison
|
||||||
"""
|
"""
|
||||||
def __ge__(self, other: typing.Any) -> typing.Any:
|
def __ge__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise greater than or equal comparison
|
element-wise greater than or equal comparison
|
||||||
"""
|
"""
|
||||||
def __add__(self, other: typing.Any) -> typing.Any:
|
def __add__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise addition
|
element-wise addition
|
||||||
"""
|
"""
|
||||||
def __radd__(self, other: typing.Any) -> typing.Any:
|
def __radd__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise addition (reflected)
|
element-wise addition (reflected)
|
||||||
"""
|
"""
|
||||||
def __sub__(self, other: typing.Any) -> typing.Any:
|
def __sub__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise subtraction
|
element-wise subtraction
|
||||||
"""
|
"""
|
||||||
def __rsub__(self, other: typing.Any) -> typing.Any:
|
def __rsub__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise subtraction (reflected)
|
element-wise subtraction (reflected)
|
||||||
"""
|
"""
|
||||||
def __mul__(self, other: typing.Any) -> typing.Any:
|
def __mul__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise multiplication
|
element-wise multiplication
|
||||||
"""
|
"""
|
||||||
def __rmul__(self, other: typing.Any) -> typing.Any:
|
def __rmul__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise multiplication (reflected)
|
element-wise multiplication (reflected)
|
||||||
"""
|
"""
|
||||||
def __truediv__(self, other: typing.Any) -> typing.Any:
|
def __truediv__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise true division
|
element-wise true division
|
||||||
"""
|
"""
|
||||||
def __rtruediv__(self, other: typing.Any) -> typing.Any:
|
def __rtruediv__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise true division (reflected)
|
element-wise true division (reflected)
|
||||||
"""
|
"""
|
||||||
def __floordiv__(self, other: typing.Any) -> typing.Any:
|
def __floordiv__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise floor division
|
element-wise floor division
|
||||||
"""
|
"""
|
||||||
def __rfloordiv__(self, other: typing.Any) -> typing.Any:
|
def __rfloordiv__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise floor division (reflected)
|
element-wise floor division (reflected)
|
||||||
"""
|
"""
|
||||||
def __mod__(self, other: typing.Any) -> typing.Any:
|
def __mod__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise modulo
|
element-wise modulo
|
||||||
"""
|
"""
|
||||||
def __rmod__(self, other: typing.Any) -> typing.Any:
|
def __rmod__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise modulo (reflected)
|
element-wise modulo (reflected)
|
||||||
"""
|
"""
|
||||||
def __matmul__(self, other: typing.Any) -> typing.Any:
|
def __matmul__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise matrix multiplication
|
element-wise matrix multiplication
|
||||||
"""
|
"""
|
||||||
def __rmatmul__(self, other: typing.Any) -> typing.Any:
|
def __rmatmul__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise matrix multiplication (reflected)
|
element-wise matrix multiplication (reflected)
|
||||||
"""
|
"""
|
||||||
def __and__(self, other: typing.Any) -> typing.Any:
|
def __and__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise AND
|
element-wise bitwise AND
|
||||||
"""
|
"""
|
||||||
def __rand__(self, other: typing.Any) -> typing.Any:
|
def __rand__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise AND (reflected)
|
element-wise bitwise AND (reflected)
|
||||||
"""
|
"""
|
||||||
def __or__(self, other: typing.Any) -> typing.Any:
|
def __or__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise OR
|
element-wise bitwise OR
|
||||||
"""
|
"""
|
||||||
def __ror__(self, other: typing.Any) -> typing.Any:
|
def __ror__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise OR (reflected)
|
element-wise bitwise OR (reflected)
|
||||||
"""
|
"""
|
||||||
def __xor__(self, other: typing.Any) -> typing.Any:
|
def __xor__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise XOR
|
element-wise bitwise XOR
|
||||||
"""
|
"""
|
||||||
def __rxor__(self, other: typing.Any) -> typing.Any:
|
def __rxor__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | bool:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise XOR (reflected)
|
element-wise bitwise XOR (reflected)
|
||||||
"""
|
"""
|
||||||
def __lshift__(self, other: typing.Any) -> typing.Any:
|
def __lshift__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise left shift
|
element-wise left shift
|
||||||
"""
|
"""
|
||||||
def __rlshift__(self, other: typing.Any) -> typing.Any:
|
def __rlshift__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise left shift (reflected)
|
element-wise left shift (reflected)
|
||||||
"""
|
"""
|
||||||
def __rshift__(self, other: typing.Any) -> typing.Any:
|
def __rshift__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise right shift
|
element-wise right shift
|
||||||
"""
|
"""
|
||||||
def __rrshift__(self, other: typing.Any) -> typing.Any:
|
def __rrshift__(
|
||||||
|
self, other: float | numpy.ArrayLike | Imread
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise right shift (reflected)
|
element-wise right shift (reflected)
|
||||||
"""
|
"""
|
||||||
def __neg__(self) -> typing.Any:
|
def __neg__(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise negation
|
element-wise negation
|
||||||
"""
|
"""
|
||||||
def __pos__(self) -> typing.Any:
|
def __pos__(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise positive
|
element-wise positive
|
||||||
"""
|
"""
|
||||||
def __abs__(self) -> typing.Any:
|
def __abs__(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise absolute value
|
element-wise absolute value
|
||||||
"""
|
"""
|
||||||
def __invert__(self) -> typing.Any:
|
def __invert__(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
element-wise bitwise inversion
|
element-wise bitwise inversion
|
||||||
"""
|
"""
|
||||||
@@ -524,13 +601,19 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
the file path as a string
|
the file path as a string
|
||||||
"""
|
"""
|
||||||
|
def frame(
|
||||||
|
self, c: builtins.int, z: builtins.int, t: builtins.int
|
||||||
|
) -> numpy.typing.NDArray | float:
|
||||||
|
r"""
|
||||||
|
retrieve a single frame at czt, sliced accordingly, alias of get_frame
|
||||||
|
"""
|
||||||
def get_frame(
|
def get_frame(
|
||||||
self, c: builtins.int, z: builtins.int, t: builtins.int
|
self, c: builtins.int, z: builtins.int, t: builtins.int
|
||||||
) -> typing.Any:
|
) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
retrieve a single frame at czt, sliced accordingly
|
retrieve a single frame at czt, sliced accordingly
|
||||||
"""
|
"""
|
||||||
def flatten(self) -> typing.Any:
|
def flatten(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
flatten the view into a 1D numpy array
|
flatten the view into a 1D numpy array
|
||||||
"""
|
"""
|
||||||
@@ -546,17 +629,21 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
find the position of an axis
|
find the position of an axis
|
||||||
"""
|
"""
|
||||||
|
def swapaxes(self, ax0: int | str, ax1: int | str) -> Imread:
|
||||||
|
r"""
|
||||||
|
swap two axes
|
||||||
|
"""
|
||||||
def swap_axes(self, ax0: int | str, ax1: int | str) -> Imread:
|
def swap_axes(self, ax0: int | str, ax1: int | str) -> Imread:
|
||||||
r"""
|
r"""
|
||||||
swap two axes
|
swap two axes
|
||||||
"""
|
"""
|
||||||
def transpose(
|
def transpose(
|
||||||
self, axes: typing.Optional[typing.Sequence[int | str]] = None
|
self, axes: typing.Optional[collections.abc.Sequence[int | str] | str] = None
|
||||||
) -> Imread:
|
) -> Imread:
|
||||||
r"""
|
r"""
|
||||||
permute the order of the axes
|
permute the order of the axes
|
||||||
"""
|
"""
|
||||||
def as_array(self) -> typing.Any:
|
def as_array(self) -> numpy.typing.NDArray | float:
|
||||||
r"""
|
r"""
|
||||||
collect data into a numpy array
|
collect data into a numpy array
|
||||||
"""
|
"""
|
||||||
@@ -586,9 +673,9 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
the detector gain for a given channel
|
the detector gain for a given channel
|
||||||
"""
|
"""
|
||||||
def summary(self) -> builtins.str:
|
def channel_name(self, channel: builtins.int) -> builtins.str:
|
||||||
r"""
|
r"""
|
||||||
gives a helpful summary of the recorded experiment
|
the name of a particular channel
|
||||||
"""
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_available_series(
|
def get_available_series(
|
||||||
@@ -692,6 +779,73 @@ class Imread:
|
|||||||
r"""
|
r"""
|
||||||
Return the sum along a given axis. Arguments beyond axis are not implemented
|
Return the sum along a given axis. Arguments beyond axis are not implemented
|
||||||
"""
|
"""
|
||||||
|
def with_interpolation_order(self, order: typing.Optional[builtins.int]) -> Imread:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
def get_default_interpolation_value(
|
||||||
|
self, channel: builtins.int
|
||||||
|
) -> transforms.DefaultValue:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
|
def set_default_interpolation_value(
|
||||||
|
self,
|
||||||
|
channel: builtins.int,
|
||||||
|
value: float | transforms.DefaultValue | collections.abc.Sequence[float],
|
||||||
|
) -> None:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
|
def with_default_interpolation_value(
|
||||||
|
self,
|
||||||
|
channel: builtins.int,
|
||||||
|
value: float | transforms.DefaultValue | collections.abc.Sequence[float],
|
||||||
|
) -> Imread:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
|
def with_transform(
|
||||||
|
self,
|
||||||
|
channels: builtins.bool = True,
|
||||||
|
drift: builtins.bool = False,
|
||||||
|
file: typing.Optional[builtins.str | os.PathLike | pathlib.Path] = None,
|
||||||
|
bead_files: typing.Optional[
|
||||||
|
typing.Sequence[builtins.str | os.PathLike | pathlib.Path]
|
||||||
|
] = None,
|
||||||
|
main_channel: typing.Optional[builtins.int] = None,
|
||||||
|
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
|
) -> Imread:
|
||||||
|
r"""
|
||||||
|
return a new view with transformations applied (channel alignment, drift correction)
|
||||||
|
backward compatibility
|
||||||
|
"""
|
||||||
|
def with_transforms(
|
||||||
|
self,
|
||||||
|
channels: builtins.bool = True,
|
||||||
|
drift: builtins.bool = False,
|
||||||
|
file: typing.Optional[builtins.str | os.PathLike | pathlib.Path] = None,
|
||||||
|
bead_files: typing.Optional[
|
||||||
|
typing.Sequence[builtins.str | os.PathLike | pathlib.Path]
|
||||||
|
] = None,
|
||||||
|
main_channel: typing.Optional[builtins.int] = None,
|
||||||
|
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
|
) -> Imread:
|
||||||
|
r"""
|
||||||
|
returns a view where channels and/or frames are registered with an affine transformation
|
||||||
|
"""
|
||||||
|
def load_transforms_from_yaml(
|
||||||
|
self, path: builtins.str | os.PathLike | pathlib.Path
|
||||||
|
) -> None: ...
|
||||||
|
def calculate_channel_transforms_2d(
|
||||||
|
self, main_channel: builtins.int
|
||||||
|
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
||||||
|
def calculate_channel_transforms_3d(
|
||||||
|
self, main_channel: builtins.int
|
||||||
|
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
||||||
|
def calculate_drift_transforms_2d(self) -> builtins.list[transforms.Transform]: ...
|
||||||
|
def calculate_drift_transforms_3d(self) -> builtins.list[transforms.Transform]: ...
|
||||||
|
|
||||||
class Shape:
|
class Shape:
|
||||||
r"""
|
r"""
|
||||||
@@ -761,7 +915,7 @@ class Shape:
|
|||||||
arguments for pickling
|
arguments for pickling
|
||||||
"""
|
"""
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self, idx: str | int | None | Ellipsis | slice | list[int] | tuple[int]
|
self, idx: str | int | None | Ellipsis | slice | typing.Sequence[int]
|
||||||
) -> typing.Optional[int | tuple[int]]:
|
) -> typing.Optional[int | tuple[int]]:
|
||||||
r"""
|
r"""
|
||||||
get dimension size by index or axis name
|
get dimension size by index or axis name
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# ruff: noqa: E501, F401, F403, F405
|
# ruff: noqa: E501, F401, F403, F405
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
|
import collections.abc
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import typing
|
import typing
|
||||||
@@ -10,6 +11,7 @@ import numpy
|
|||||||
import numpy.typing
|
import numpy.typing
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"DefaultValue",
|
||||||
"Optimizer",
|
"Optimizer",
|
||||||
"RegistrationStep",
|
"RegistrationStep",
|
||||||
"SamplingArg",
|
"SamplingArg",
|
||||||
@@ -18,6 +20,21 @@ __all__ = [
|
|||||||
"Transforms",
|
"Transforms",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class DefaultValue:
|
||||||
|
@property
|
||||||
|
def values(self) -> builtins.list[builtins.float]: ...
|
||||||
|
@property
|
||||||
|
def weights(self) -> builtins.list[builtins.float]: ...
|
||||||
|
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
values: typing.Sequence[builtins.float],
|
||||||
|
weights: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
|
) -> DefaultValue: ...
|
||||||
|
def __getnewargs__(
|
||||||
|
self,
|
||||||
|
) -> tuple[builtins.list[builtins.float], builtins.list[builtins.float]]: ...
|
||||||
|
|
||||||
class Optimizer:
|
class Optimizer:
|
||||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||||
def __new__(cls, optimizer: builtins.str) -> Optimizer: ...
|
def __new__(cls, optimizer: builtins.str) -> Optimizer: ...
|
||||||
@@ -116,24 +133,6 @@ class Transform:
|
|||||||
@dparameters.setter
|
@dparameters.setter
|
||||||
def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||||
@property
|
@property
|
||||||
def center(self) -> builtins.list[builtins.float]: ...
|
|
||||||
@center.setter
|
|
||||||
def center(self, value: typing.Sequence[builtins.float]) -> None: ...
|
|
||||||
@property
|
|
||||||
def origin(self) -> builtins.list[builtins.float]:
|
|
||||||
r"""
|
|
||||||
backwards compatibility
|
|
||||||
"""
|
|
||||||
@origin.setter
|
|
||||||
def origin(self, value: typing.Sequence[builtins.float]) -> None:
|
|
||||||
r"""
|
|
||||||
backwards compatibility
|
|
||||||
"""
|
|
||||||
@property
|
|
||||||
def shape(self) -> builtins.list[builtins.int]: ...
|
|
||||||
@shape.setter
|
|
||||||
def shape(self, value: typing.Sequence[builtins.int]) -> None: ...
|
|
||||||
@property
|
|
||||||
def ndim(self) -> builtins.int: ...
|
def ndim(self) -> builtins.int: ...
|
||||||
@property
|
@property
|
||||||
def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||||
@@ -149,18 +148,12 @@ class Transform:
|
|||||||
def __new__(
|
def __new__(
|
||||||
cls,
|
cls,
|
||||||
parameters: typing.Optional[typing.Sequence[builtins.float]] = None,
|
parameters: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
shape: typing.Optional[typing.Sequence[builtins.int]] = None,
|
dparameters: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
center: typing.Optional[typing.Sequence[builtins.float]] = None,
|
center: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||||
) -> Transform: ...
|
) -> Transform: ...
|
||||||
def __getnewargs__(
|
def __getnewargs__(
|
||||||
self,
|
self,
|
||||||
) -> tuple[
|
) -> tuple[builtins.list[builtins.float], builtins.list[builtins.float]]: ...
|
||||||
builtins.list[builtins.float],
|
|
||||||
builtins.list[builtins.int],
|
|
||||||
typing.Optional[builtins.list[builtins.float]],
|
|
||||||
]: ...
|
|
||||||
def __getstate__(self) -> builtins.list[builtins.float]: ...
|
|
||||||
def __setstate__(self, state: typing.Sequence[builtins.float]) -> None: ...
|
|
||||||
def __add__(self, other: Transform) -> Transform: ...
|
def __add__(self, other: Transform) -> Transform: ...
|
||||||
def __radd__(self, other: Transform) -> Transform: ...
|
def __radd__(self, other: Transform) -> Transform: ...
|
||||||
def __sub__(self, other: Transform) -> Transform: ...
|
def __sub__(self, other: Transform) -> Transform: ...
|
||||||
@@ -168,11 +161,6 @@ class Transform:
|
|||||||
def __mul__(self, other: Transform | float) -> Transform: ...
|
def __mul__(self, other: Transform | float) -> Transform: ...
|
||||||
def __rmul__(self, other: Transform | float) -> Transform: ...
|
def __rmul__(self, other: Transform | float) -> Transform: ...
|
||||||
def __truediv__(self, other: builtins.float) -> Transform: ...
|
def __truediv__(self, other: builtins.float) -> Transform: ...
|
||||||
def adapt(
|
|
||||||
self,
|
|
||||||
center: typing.Sequence[builtins.float],
|
|
||||||
shape: typing.Sequence[builtins.int],
|
|
||||||
) -> None: ...
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -189,23 +177,32 @@ class Transform:
|
|||||||
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||||
) -> Transform: ...
|
) -> Transform: ...
|
||||||
def interpolate(
|
def interpolate(
|
||||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
self,
|
||||||
|
order: builtins.int,
|
||||||
|
image: numpy.typing.ArrayLike,
|
||||||
|
default_value: typing.Optional[DefaultValue] = None,
|
||||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||||
def interpolate_par(
|
def interpolate_par(
|
||||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
self,
|
||||||
|
order: builtins.int,
|
||||||
|
image: numpy.typing.ArrayLike,
|
||||||
|
default_value: typing.Optional[DefaultValue] = None,
|
||||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||||
def frame(
|
def frame(
|
||||||
self, image: numpy.typing.ArrayLike, default: typing.Optional[typing.Any] = None
|
self,
|
||||||
|
image: numpy.typing.ArrayLike,
|
||||||
|
default: typing.Optional[typing.Any] = None,
|
||||||
|
default_value: typing.Optional[DefaultValue] = None,
|
||||||
) -> numpy.ndarray:
|
) -> numpy.ndarray:
|
||||||
r"""
|
r"""
|
||||||
backwards compatibility, default parameter is not implemented
|
backwards compatibility, default parameter is not implemented
|
||||||
"""
|
"""
|
||||||
def is_unity(self) -> builtins.bool: ...
|
def is_unity(self) -> builtins.bool: ...
|
||||||
def transform_point(
|
def transform_point(
|
||||||
self, point: numpy.typing.ArrayLike
|
self, point: numpy.typing.ArrayLike, center: numpy.typing.ArrayLike
|
||||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||||
def transform_points(
|
def transform_points(
|
||||||
self, points: numpy.typing.ArrayLike
|
self, points: numpy.typing.ArrayLike, center: numpy.typing.ArrayLike
|
||||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def register(
|
def register(
|
||||||
@@ -222,9 +219,25 @@ class Transform:
|
|||||||
def register_translation(
|
def register_translation(
|
||||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||||
) -> Transform: ...
|
) -> Transform: ...
|
||||||
|
@staticmethod
|
||||||
|
def register_translation_fft(
|
||||||
|
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||||
|
) -> Transform: ...
|
||||||
|
|
||||||
class Transforms:
|
class Transforms:
|
||||||
@property
|
@property
|
||||||
|
def interpolation_order(self) -> typing.Optional[builtins.int]:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms,
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
@interpolation_order.setter
|
||||||
|
def interpolation_order(self, value: typing.Optional[builtins.int]) -> None:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
@property
|
||||||
def channel_transforms(self) -> builtins.dict[builtins.str, Transform]: ...
|
def channel_transforms(self) -> builtins.dict[builtins.str, Transform]: ...
|
||||||
@channel_transforms.setter
|
@channel_transforms.setter
|
||||||
def channel_transforms(
|
def channel_transforms(
|
||||||
@@ -239,10 +252,48 @@ class Transforms:
|
|||||||
cls,
|
cls,
|
||||||
channel: typing.Mapping[builtins.str, Transform] = {},
|
channel: typing.Mapping[builtins.str, Transform] = {},
|
||||||
drift: typing.Sequence[Transform] = [],
|
drift: typing.Sequence[Transform] = [],
|
||||||
|
interpolation_order: typing.Optional[builtins.int] = None,
|
||||||
|
default_value: typing.Optional[
|
||||||
|
typing.Mapping[builtins.str, DefaultValue]
|
||||||
|
] = None,
|
||||||
) -> Transforms: ...
|
) -> Transforms: ...
|
||||||
def __getnewargs__(
|
def __getnewargs__(
|
||||||
self,
|
self,
|
||||||
) -> tuple[builtins.dict[builtins.str, Transform], builtins.list[Transform]]: ...
|
) -> tuple[
|
||||||
|
builtins.dict[builtins.str, Transform],
|
||||||
|
builtins.list[Transform],
|
||||||
|
typing.Optional[builtins.int],
|
||||||
|
builtins.dict[builtins.str, DefaultValue],
|
||||||
|
]: ...
|
||||||
|
def with_interpolation_order(
|
||||||
|
self, order: typing.Optional[builtins.int]
|
||||||
|
) -> Transforms:
|
||||||
|
r"""
|
||||||
|
the bspline order used for interpolation when using transforms
|
||||||
|
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
"""
|
||||||
|
def get_default_interpolation_value(
|
||||||
|
self, channel_name: builtins.str
|
||||||
|
) -> DefaultValue:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
|
def set_default_interpolation_value(
|
||||||
|
self,
|
||||||
|
channel_name: builtins.str,
|
||||||
|
value: float | DefaultValue | collections.abc.Sequence[float],
|
||||||
|
) -> None:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
|
def with_default_interpolation_value(
|
||||||
|
self,
|
||||||
|
channel_name: builtins.str,
|
||||||
|
value: float | DefaultValue | collections.abc.Sequence[float],
|
||||||
|
) -> Transforms:
|
||||||
|
r"""
|
||||||
|
the value of the interpolated image outside the data
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
||||||
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .ndbioimage_rs.transforms import (
|
from .ndbioimage_rs.transforms import (
|
||||||
|
DefaultValue,
|
||||||
Optimizer,
|
Optimizer,
|
||||||
RegistrationStep,
|
RegistrationStep,
|
||||||
SamplingArg,
|
SamplingArg,
|
||||||
@@ -8,6 +9,7 @@ from .ndbioimage_rs.transforms import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"DefaultValue",
|
||||||
"Optimizer",
|
"Optimizer",
|
||||||
"RegistrationStep",
|
"RegistrationStep",
|
||||||
"SamplingArg",
|
"SamplingArg",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2027.0.5"
|
version = "2027.0.8"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
@@ -35,7 +35,7 @@ ndbioimage_generate_stub = "ndbioimage:ndbioimage_generate_stub"
|
|||||||
|
|
||||||
[tool.maturin]
|
[tool.maturin]
|
||||||
python-source = "py"
|
python-source = "py"
|
||||||
features = ["python", "bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "tiffwrite", "movie", "transforms"]
|
features = ["python", "bioformats_java", "gpl-formats", "bioformats_rust", "czi", "tiff", "tiffseq", "tiffwrite", "movie", "transforms"]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
module-name = "ndbioimage.ndbioimage_rs"
|
module-name = "ndbioimage.ndbioimage_rs"
|
||||||
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
||||||
|
|||||||
+18
-9
@@ -196,11 +196,11 @@ impl<'de> DeserializeAs<'de, SliceInfoElem> for SliceInfoElemDef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub(crate) struct Slice {
|
pub(crate) struct Slice {
|
||||||
start: isize,
|
pub(crate) start: isize,
|
||||||
end: isize,
|
pub(crate) end: isize,
|
||||||
step: isize,
|
pub(crate) step: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slice {
|
impl Slice {
|
||||||
@@ -236,7 +236,7 @@ impl IntoIterator for &Slice {
|
|||||||
type IntoIter = Slice;
|
type IntoIter = Slice;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.clone()
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,16 +314,16 @@ impl Index<&Axis> for Shape {
|
|||||||
impl Index<usize> for Shape {
|
impl Index<usize> for Shape {
|
||||||
type Output = usize;
|
type Output = usize;
|
||||||
|
|
||||||
fn index(&self, dim: usize) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
&self[self.order[dim % self.order.len()]]
|
&self[self.order[index]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<&usize> for Shape {
|
impl Index<&usize> for Shape {
|
||||||
type Output = usize;
|
type Output = usize;
|
||||||
|
|
||||||
fn index(&self, dim: &usize) -> &Self::Output {
|
fn index(&self, index: &usize) -> &Self::Output {
|
||||||
&self[self.order[dim % self.order.len()]]
|
&self[self.order[*index]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +406,10 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: &usize) -> Option<&usize> {
|
||||||
|
self.order.get(*index).map(|axis| &self[axis])
|
||||||
|
}
|
||||||
|
|
||||||
/// the number of axes in the shape
|
/// the number of axes in the shape
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.order.len()
|
self.order.len()
|
||||||
@@ -421,6 +425,11 @@ impl Shape {
|
|||||||
self.order.iter().map(|axis| self[axis]).collect()
|
self.order.iter().map(|axis| self[axis]).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the sizes in cztyx order
|
||||||
|
pub fn cztyx(&self) -> [usize; 5] {
|
||||||
|
[self.c, self.z, self.t, self.y, self.x]
|
||||||
|
}
|
||||||
|
|
||||||
/// the sizes along the axes as a map from axis to size
|
/// the sizes along the axes as a map from axis to size
|
||||||
pub fn to_hashmap(&self) -> HashMap<Axis, usize> {
|
pub fn to_hashmap(&self) -> HashMap<Axis, usize> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|||||||
+108
-6
@@ -1,15 +1,23 @@
|
|||||||
use crate::axes::{Axis, Operation};
|
use crate::axes::{Axis, Operation};
|
||||||
|
use crate::readers::Frame;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use crate::transforms::{DefaultValues, Transforms};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use image_registration::transform::Transform;
|
||||||
use indexmap::{Equivalent, IndexMap};
|
use indexmap::{Equivalent, IndexMap};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use ndarray::IxDyn;
|
||||||
use ndarray::{ArrayD, SliceInfoElem};
|
use ndarray::{ArrayD, SliceInfoElem};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::cmp::PartialEq;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::readers::Frame;
|
|
||||||
|
|
||||||
/// maximum number of frames held in the cache
|
/// maximum number of frames held in the cache
|
||||||
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
||||||
|
|
||||||
@@ -132,7 +140,33 @@ impl ReaderKeyRef<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type FrameKey = (ReaderKey, usize, usize, usize);
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) struct FrameKey {
|
||||||
|
pub(crate) reader: ReaderKey,
|
||||||
|
pub(crate) c: usize,
|
||||||
|
pub(crate) z: usize,
|
||||||
|
pub(crate) t: usize,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transform: Option<(Transform<IxDyn>, usize, DefaultValues)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for FrameKey {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.reader.hash(state);
|
||||||
|
self.c.hash(state);
|
||||||
|
self.z.hash(state);
|
||||||
|
self.t.hash(state);
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
if let Some(transform) = self.transform.as_ref() {
|
||||||
|
transform.0.hash(state);
|
||||||
|
transform.1.hash(state);
|
||||||
|
transform.2.iter().for_each(|(k, v)| {
|
||||||
|
k.hash(state);
|
||||||
|
v.hash(state);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -141,6 +175,8 @@ pub(crate) struct FrameKeyRef<'a> {
|
|||||||
pub(crate) c: usize,
|
pub(crate) c: usize,
|
||||||
pub(crate) z: usize,
|
pub(crate) z: usize,
|
||||||
pub(crate) t: usize,
|
pub(crate) t: usize,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transform: Option<(Cow<'a, Transform<IxDyn>>, usize, Cow<'a, DefaultValues>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for FrameKeyRef<'_> {
|
impl Hash for FrameKeyRef<'_> {
|
||||||
@@ -149,19 +185,60 @@ impl Hash for FrameKeyRef<'_> {
|
|||||||
self.c.hash(state);
|
self.c.hash(state);
|
||||||
self.z.hash(state);
|
self.z.hash(state);
|
||||||
self.t.hash(state);
|
self.t.hash(state);
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
if let Some(transform) = self.transform.as_ref() {
|
||||||
|
transform.0.hash(state);
|
||||||
|
transform.1.hash(state);
|
||||||
|
transform.2.iter().for_each(|(k, v)| {
|
||||||
|
k.hash(state);
|
||||||
|
v.hash(state);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||||
let (rk, c, z, t) = key;
|
let FrameKey {
|
||||||
self.reader.equivalent(rk) && self.c == *c && self.z == *z && self.t == *t
|
reader: reader_key,
|
||||||
|
c,
|
||||||
|
z,
|
||||||
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transform,
|
||||||
|
} = key;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(reader_key)
|
||||||
|
&& self.c == *c
|
||||||
|
&& self.z == *z
|
||||||
|
&& self.t == *t
|
||||||
|
&& self
|
||||||
|
.transform
|
||||||
|
.as_ref()
|
||||||
|
.map(|(t, i, d)| (t.as_ref(), i, d.as_ref()))
|
||||||
|
== transform.as_ref().map(|(t, i, d)| (t, i, d))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(reader_key) && self.c == *c && self.z == *z && self.t == *t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameKeyRef<'_> {
|
impl FrameKeyRef<'_> {
|
||||||
pub(crate) fn to_owned(&self) -> FrameKey {
|
pub(crate) fn to_owned(&self) -> FrameKey {
|
||||||
(self.reader.to_owned(), self.c, self.z, self.t)
|
FrameKey {
|
||||||
|
reader: self.reader.to_owned(),
|
||||||
|
c: self.c,
|
||||||
|
z: self.z,
|
||||||
|
t: self.t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transform: self
|
||||||
|
.transform
|
||||||
|
.as_ref()
|
||||||
|
.map(|(t, i, d)| (t.clone().into_owned(), *i, d.clone().into_owned())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +250,8 @@ pub(crate) struct ArrayKey {
|
|||||||
pub(crate) slice: Vec<SliceInfoElem>,
|
pub(crate) slice: Vec<SliceInfoElem>,
|
||||||
pub(crate) axes: Vec<Axis>,
|
pub(crate) axes: Vec<Axis>,
|
||||||
pub(crate) operations: Vec<(Axis, Operation)>,
|
pub(crate) operations: Vec<(Axis, Operation)>,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transforms: Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
||||||
@@ -183,6 +262,8 @@ pub(crate) struct ArrayKeyRef<'a> {
|
|||||||
pub(crate) slice: &'a [SliceInfoElem],
|
pub(crate) slice: &'a [SliceInfoElem],
|
||||||
pub(crate) axes: &'a [Axis],
|
pub(crate) axes: &'a [Axis],
|
||||||
pub(crate) operations: &'a IndexMap<Axis, Operation>,
|
pub(crate) operations: &'a IndexMap<Axis, Operation>,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transforms: &'a Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for ArrayKeyRef<'_> {
|
impl Hash for ArrayKeyRef<'_> {
|
||||||
@@ -196,11 +277,15 @@ impl Hash for ArrayKeyRef<'_> {
|
|||||||
ax.hash(state);
|
ax.hash(state);
|
||||||
op.hash(state);
|
op.hash(state);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
self.transforms.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
||||||
fn equivalent(&self, key: &ArrayKey) -> bool {
|
fn equivalent(&self, key: &ArrayKey) -> bool {
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
{
|
||||||
self.reader.equivalent(&key.reader)
|
self.reader.equivalent(&key.reader)
|
||||||
&& self.dtype == key.dtype
|
&& self.dtype == key.dtype
|
||||||
&& self.slice == key.slice
|
&& self.slice == key.slice
|
||||||
@@ -211,6 +296,21 @@ impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
|||||||
.iter()
|
.iter()
|
||||||
.zip(&key.operations)
|
.zip(&key.operations)
|
||||||
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||||
|
&& self.transforms == key.transforms
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(&key.reader)
|
||||||
|
&& self.dtype == key.dtype
|
||||||
|
&& self.slice == key.slice
|
||||||
|
&& self.axes == key.axes
|
||||||
|
&& self.operations.len() == key.operations.len()
|
||||||
|
&& self
|
||||||
|
.operations
|
||||||
|
.iter()
|
||||||
|
.zip(&key.operations)
|
||||||
|
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +326,8 @@ impl ArrayKeyRef<'_> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(ax, op)| (*ax, op.clone()))
|
.map(|(ax, op)| (*ax, op.clone()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transforms: self.transforms.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -65,10 +65,6 @@ pub enum Error {
|
|||||||
#[cfg(feature = "czi")]
|
#[cfg(feature = "czi")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
XmlTreeParse(#[from] xmltree::ParseError),
|
XmlTreeParse(#[from] xmltree::ParseError),
|
||||||
/// a czi-specific error
|
|
||||||
#[cfg(feature = "czi")]
|
|
||||||
#[error(transparent)]
|
|
||||||
Czi(#[from] crate::readers::czi::CziError),
|
|
||||||
/// an error joining a tokio task
|
/// an error joining a tokio task
|
||||||
#[cfg(feature = "movie")]
|
#[cfg(feature = "movie")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -121,6 +117,9 @@ pub enum Error {
|
|||||||
/// the view is empty
|
/// the view is empty
|
||||||
#[error("empty view")]
|
#[error("empty view")]
|
||||||
EmptyView,
|
EmptyView,
|
||||||
|
/// the view cannot be parsed as a scalar
|
||||||
|
#[error("not a scalar")]
|
||||||
|
NotScalar,
|
||||||
/// the color string could not be parsed
|
/// the color string could not be parsed
|
||||||
#[error("invalid color: {0}")]
|
#[error("invalid color: {0}")]
|
||||||
InvalidColor(String),
|
InvalidColor(String),
|
||||||
@@ -173,12 +172,23 @@ pub enum Error {
|
|||||||
#[error("{0} do not match in {1} and {2}")]
|
#[error("{0} do not match in {1} and {2}")]
|
||||||
FileMismatch(String, String, String),
|
FileMismatch(String, String, String),
|
||||||
/// a jvm created in another process was inherited across a fork
|
/// a jvm created in another process was inherited across a fork
|
||||||
#[cfg(feature = "bioformats_java")]
|
|
||||||
#[error(
|
#[error(
|
||||||
"the jvm was created in process {0} but this is process {1}: a forked child inherits a jvm \
|
"the jvm was created in process {0} but this is process {1}: a forked child inherits a jvm \
|
||||||
it cannot use. read the file before forking, or start the child with spawn/exec instead."
|
it cannot use. read the file before forking, or start the child with spawn/exec instead."
|
||||||
)]
|
)]
|
||||||
|
/// jvm was forked, so it cannot run in this process
|
||||||
ForkedJvm(u32, u32),
|
ForkedJvm(u32, u32),
|
||||||
|
/// only 2d and 3d transformations are implemented
|
||||||
|
#[error("{0}D transformation is not implemented")]
|
||||||
|
TransformationNotImplemented(usize),
|
||||||
|
/// cannot find the name for some channel because it does not exist
|
||||||
|
#[error("no channel {0} present")]
|
||||||
|
NoChannel(usize),
|
||||||
|
/// no valid blocks in czi file
|
||||||
|
#[error("czi file has no valid blocks")]
|
||||||
|
CziNoValidBlocks,
|
||||||
|
#[error("f128 is not supported")]
|
||||||
|
F128NotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//! structure. Additionally, it can automatically calculate an affine transform that corrects for
|
//! structure. Additionally, it can automatically calculate an affine transform that corrects for
|
||||||
//! chromatic aberrations etc. and apply it on the fly to the image.
|
//! chromatic aberrations etc. and apply it on the fly to the image.
|
||||||
//!
|
//!
|
||||||
//! Currently, it supports imagej tif files, czi files, micromanager tif sequences and anything
|
//! Currently, it supports imagej and ome tif files, czi files, micromanager tif sequences and
|
||||||
//! [bioformats](https://www.openmicroscopy.org/bio-formats/) can handle.
|
//! anything [bioformats](https://www.openmicroscopy.org/bio-formats/) can handle.
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use ndarray::Array2;
|
//! use ndarray::Array2;
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
||||||
//! let path = "/path/to/file";
|
//! let path = "/path/to/file";
|
||||||
//! let reader = DynReader::new(&path, 0, 0)?;
|
//! let reader = DynReader::new(&path, 0, 0)?;
|
||||||
//! println!("shape: {}", reader.shape());
|
//! println!("shape: {}", reader.raw_shape());
|
||||||
//! let frame = reader.get_frame(0, 0, 0)?;
|
//! let frame = reader.get_frame(0, 0, 0)?;
|
||||||
//! if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
//! if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||||
//! println!("{:?}", arr);
|
//! println!("{:?}", arr);
|
||||||
@@ -70,7 +70,7 @@ pub mod readers;
|
|||||||
pub mod tiffwrite;
|
pub mod tiffwrite;
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
/// image registration and warping images using affine transforms
|
/// image registration and warping images using affine transforms
|
||||||
mod transforms;
|
pub mod transforms;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
/// main entry point for the application
|
/// main entry point for the application
|
||||||
@@ -338,7 +338,7 @@ mod tests {
|
|||||||
fn read_ser() -> Result<(), Error> {
|
fn read_ser() -> Result<(), Error> {
|
||||||
let file = "czi/Experiment-2029.czi";
|
let file = "czi/Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
println!("shape: {}", reader.shape());
|
println!("shape: {}", reader.raw_shape());
|
||||||
let frame = reader.get_frame(0, 0, 0)?;
|
let frame = reader.get_frame(0, 0, 0)?;
|
||||||
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||||
println!("{:?}", arr);
|
println!("{:?}", arr);
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ fn main() -> PyResult<()> {
|
|||||||
#[pymodule]
|
#[pymodule]
|
||||||
#[pyo3(name = "ndbioimage_rs")]
|
#[pyo3(name = "ndbioimage_rs")]
|
||||||
mod ndbioimage_rs {
|
mod ndbioimage_rs {
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[pymodule_export]
|
#[pymodule_export]
|
||||||
@@ -100,6 +101,9 @@ mod ndbioimage_rs {
|
|||||||
#[pymodule_export]
|
#[pymodule_export]
|
||||||
use super::super::transforms::PyTransforms;
|
use super::super::transforms::PyTransforms;
|
||||||
|
|
||||||
|
#[pymodule_export]
|
||||||
|
use super::super::transforms::PyDefaultValue;
|
||||||
|
|
||||||
#[pymodule_export]
|
#[pymodule_export]
|
||||||
use super::super::transforms::PyRegistrationStep;
|
use super::super::transforms::PyRegistrationStep;
|
||||||
|
|
||||||
|
|||||||
+1409
-630
File diff suppressed because it is too large.
Load diff
+514
-110
@@ -1,5 +1,7 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::py::imread::PyView;
|
||||||
use crate::transforms::Transforms;
|
use crate::transforms::Transforms;
|
||||||
|
use image_registration::bspline::DefaultValue;
|
||||||
use image_registration::metric::{SamplingArg, Sigma};
|
use image_registration::metric::{SamplingArg, Sigma};
|
||||||
use image_registration::register::{Optimizer, RegistrationStep};
|
use image_registration::register::{Optimizer, RegistrationStep};
|
||||||
use image_registration::transform::Transform;
|
use image_registration::transform::Transform;
|
||||||
@@ -17,6 +19,45 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[gen_stub_pyclass]
|
||||||
|
#[pyclass(
|
||||||
|
subclass,
|
||||||
|
from_py_object,
|
||||||
|
eq,
|
||||||
|
name = "DefaultValue",
|
||||||
|
module = "ndbioimage.transforms"
|
||||||
|
)]
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub(crate) struct PyDefaultValue {
|
||||||
|
inner: DefaultValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pymethods]
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDefaultValue {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (values, weights = None))]
|
||||||
|
fn new(values: Vec<f64>, weights: Option<Vec<f64>>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: DefaultValue::new(values, weights.unwrap_or_default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn values(&self) -> Vec<f64> {
|
||||||
|
self.inner.values().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn weights(&self) -> Vec<f64> {
|
||||||
|
self.inner.weights().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getnewargs__(&self) -> (Vec<f64>, Vec<f64>) {
|
||||||
|
(self.inner.values().to_vec(), self.inner.weights().to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gen_stub_pyclass]
|
#[gen_stub_pyclass]
|
||||||
#[pyclass(
|
#[pyclass(
|
||||||
subclass,
|
subclass,
|
||||||
@@ -34,41 +75,31 @@ pub(crate) struct PyTransform {
|
|||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyTransform {
|
impl PyTransform {
|
||||||
#[new]
|
#[new]
|
||||||
#[pyo3(signature = (parameters = None, shape = None, center = None))]
|
#[pyo3(signature = (parameters = None, dparameters = None, center = None))]
|
||||||
fn new(
|
fn new(
|
||||||
parameters: Option<Vec<f64>>,
|
parameters: Option<Vec<f64>>,
|
||||||
shape: Option<Vec<usize>>,
|
dparameters: Option<Vec<f64>>,
|
||||||
center: Option<Vec<f64>>,
|
center: Option<Vec<f64>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let parameters = parameters.unwrap_or_else(|| vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
|
let parameters = parameters.unwrap_or_else(|| vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
|
||||||
let shape = shape.unwrap_or_else(|| vec![512, 512]);
|
let mut transform = if let Some(center) = center {
|
||||||
if let Some(center) = center {
|
Transform::new_around_center(parameters, ¢er)
|
||||||
Self {
|
|
||||||
inner: Transform::new_with_center(parameters, center, shape),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Transform::new(parameters)
|
||||||
inner: Transform::new(parameters, shape),
|
};
|
||||||
}
|
if let Some(dparameters) = dparameters {
|
||||||
|
transform.dparameters = dparameters;
|
||||||
}
|
}
|
||||||
|
Self { inner: transform }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getnewargs__(&self) -> (Vec<f64>, Vec<usize>, Option<Vec<f64>>) {
|
fn __getnewargs__(&self) -> (Vec<f64>, Vec<f64>) {
|
||||||
(
|
(
|
||||||
self.inner.parameters.clone(),
|
self.inner.parameters.clone(),
|
||||||
self.inner.shape.clone(),
|
self.inner.dparameters.clone(),
|
||||||
Some(self.inner.center.clone()),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getstate__(&self) -> Vec<f64> {
|
|
||||||
self.inner.dparameters.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn __setstate__(&mut self, state: Vec<f64>) {
|
|
||||||
self.inner.dparameters = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn __add__(&self, other: &PyTransform) -> PyTransform {
|
fn __add__(&self, other: &PyTransform) -> PyTransform {
|
||||||
PyTransform {
|
PyTransform {
|
||||||
inner: &self.inner + &other.inner,
|
inner: &self.inner + &other.inner,
|
||||||
@@ -98,9 +129,9 @@ impl PyTransform {
|
|||||||
py: Python,
|
py: Python,
|
||||||
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||||
) -> PyResult<PyTransform> {
|
) -> PyResult<PyTransform> {
|
||||||
if other.is_instance_of::<PyTransform>() {
|
if let Ok(other) = other.extract::<PyTransform>() {
|
||||||
Ok(PyTransform {
|
Ok(PyTransform {
|
||||||
inner: &self.inner * &other.extract::<PyTransform>()?.inner,
|
inner: &self.inner * other.inner,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let builtins = PyModule::import(py, "builtins")?;
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
@@ -116,9 +147,9 @@ impl PyTransform {
|
|||||||
py: Python,
|
py: Python,
|
||||||
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||||
) -> PyResult<PyTransform> {
|
) -> PyResult<PyTransform> {
|
||||||
if other.is_instance_of::<PyTransform>() {
|
if let Ok(other) = other.extract::<PyTransform>() {
|
||||||
Ok(PyTransform {
|
Ok(PyTransform {
|
||||||
inner: &other.extract::<PyTransform>()?.inner * &self.inner,
|
inner: &other.inner * &self.inner,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let builtins = PyModule::import(py, "builtins")?;
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
@@ -157,38 +188,6 @@ impl PyTransform {
|
|||||||
self.inner.dparameters = dparameters;
|
self.inner.dparameters = dparameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
|
||||||
fn get_center(&self) -> Vec<f64> {
|
|
||||||
self.inner.center.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[setter]
|
|
||||||
fn set_center(&mut self, center: Vec<f64>) {
|
|
||||||
self.inner.center = center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// backwards compatibility
|
|
||||||
#[getter]
|
|
||||||
fn get_origin(&self) -> Vec<f64> {
|
|
||||||
self.inner.center.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// backwards compatibility
|
|
||||||
#[setter]
|
|
||||||
fn set_origin(&mut self, center: Vec<f64>) {
|
|
||||||
self.inner.center = center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[getter]
|
|
||||||
fn get_shape(&self) -> Vec<usize> {
|
|
||||||
self.inner.shape.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[setter]
|
|
||||||
fn set_shape(&mut self, shape: Vec<usize>) {
|
|
||||||
self.inner.shape = shape;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn get_ndim(&self) -> usize {
|
fn get_ndim(&self) -> usize {
|
||||||
self.inner.ndim()
|
self.inner.ndim()
|
||||||
@@ -231,10 +230,6 @@ impl PyTransform {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adapt(&mut self, center: Vec<f64>, shape: Vec<usize>) {
|
|
||||||
self.inner.adapt(center.as_slice(), shape.as_slice());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn from_scaling(scaling: Vec<f64>) -> PyTransform {
|
fn from_scaling(scaling: Vec<f64>) -> PyTransform {
|
||||||
PyTransform {
|
PyTransform {
|
||||||
@@ -275,68 +270,86 @@ impl PyTransform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (order, image, default_value = None))]
|
||||||
fn interpolate<'py>(
|
fn interpolate<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
order: usize,
|
order: usize,
|
||||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
default_value: Option<PyDefaultValue>,
|
||||||
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||||
let image = image.as_array();
|
let image = image.as_array();
|
||||||
|
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||||
Ok(py
|
Ok(py
|
||||||
.detach(|| match order {
|
.detach(|| match order {
|
||||||
0 => Ok(self
|
0 => Ok(self.inner.interpolate::<0, _, _>(
|
||||||
.inner
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.interpolate::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
&default_value,
|
||||||
1 => Ok(self
|
)),
|
||||||
.inner
|
1 => Ok(self.inner.interpolate::<1, _, _>(
|
||||||
.interpolate::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
2 => Ok(self
|
&default_value,
|
||||||
.inner
|
)),
|
||||||
.interpolate::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
2 => Ok(self.inner.interpolate::<2, _, _>(
|
||||||
3 => Ok(self
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.inner
|
&default_value,
|
||||||
.interpolate::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
)),
|
||||||
4 => Ok(self
|
3 => Ok(self.inner.interpolate::<3, _, _>(
|
||||||
.inner
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.interpolate::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
&default_value,
|
||||||
5 => Ok(self
|
)),
|
||||||
.inner
|
4 => Ok(self.inner.interpolate::<4, _, _>(
|
||||||
.interpolate::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
|
&default_value,
|
||||||
|
)),
|
||||||
|
5 => Ok(self.inner.interpolate::<5, _, _>(
|
||||||
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
|
&default_value,
|
||||||
|
)),
|
||||||
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||||
})?
|
})?
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.into_pyarray(py))
|
.into_pyarray(py))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (order, image, default_value = None))]
|
||||||
fn interpolate_par<'py>(
|
fn interpolate_par<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
order: usize,
|
order: usize,
|
||||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
default_value: Option<PyDefaultValue>,
|
||||||
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||||
let image = image.as_array();
|
let image = image.as_array();
|
||||||
|
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||||
Ok(py
|
Ok(py
|
||||||
.detach(|| match order {
|
.detach(|| match order {
|
||||||
0 => Ok(self
|
0 => Ok(self.inner.interpolate_par::<0, _, _>(
|
||||||
.inner
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.interpolate_par::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
&default_value,
|
||||||
1 => Ok(self
|
)),
|
||||||
.inner
|
1 => Ok(self.inner.interpolate_par::<1, _, _>(
|
||||||
.interpolate_par::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
2 => Ok(self
|
&default_value,
|
||||||
.inner
|
)),
|
||||||
.interpolate_par::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
2 => Ok(self.inner.interpolate_par::<2, _, _>(
|
||||||
3 => Ok(self
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.inner
|
&default_value,
|
||||||
.interpolate_par::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
)),
|
||||||
4 => Ok(self
|
3 => Ok(self.inner.interpolate_par::<3, _, _>(
|
||||||
.inner
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
.interpolate_par::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
&default_value,
|
||||||
5 => Ok(self
|
)),
|
||||||
.inner
|
4 => Ok(self.inner.interpolate_par::<4, _, _>(
|
||||||
.interpolate_par::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
|
&default_value,
|
||||||
|
)),
|
||||||
|
5 => Ok(self.inner.interpolate_par::<5, _, _>(
|
||||||
|
image.into_dimensionality().map_err(Error::from)?,
|
||||||
|
&default_value,
|
||||||
|
)),
|
||||||
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||||
})?
|
})?
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
@@ -345,13 +358,14 @@ impl PyTransform {
|
|||||||
|
|
||||||
/// backwards compatibility, default parameter is not implemented
|
/// backwards compatibility, default parameter is not implemented
|
||||||
#[gen_stub(override_return_type(type_repr="numpy.ndarray", imports=("numpy")))]
|
#[gen_stub(override_return_type(type_repr="numpy.ndarray", imports=("numpy")))]
|
||||||
#[pyo3(signature = (image, default = None))]
|
#[pyo3(signature = (image, default = None, default_value = None))]
|
||||||
fn frame<'py>(
|
fn frame<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
image: Bound<'py, PyUntypedArray>,
|
image: Bound<'py, PyUntypedArray>,
|
||||||
default: Option<Bound<'py, PyAny>>,
|
default: Option<Bound<'py, PyAny>>,
|
||||||
|
default_value: Option<PyDefaultValue>,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
if default.is_some() {
|
if default.is_some() {
|
||||||
return Err(PyNotImplementedError::new_err(
|
return Err(PyNotImplementedError::new_err(
|
||||||
@@ -365,6 +379,7 @@ impl PyTransform {
|
|||||||
self.inner.ndim()
|
self.inner.ndim()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||||
|
|
||||||
macro_rules! interp {
|
macro_rules! interp {
|
||||||
($order:expr, $t:tt) => {
|
($order:expr, $t:tt) => {
|
||||||
@@ -375,6 +390,7 @@ impl PyTransform {
|
|||||||
.into_dimensionality()
|
.into_dimensionality()
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.view(),
|
.view(),
|
||||||
|
&default_value,
|
||||||
)
|
)
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.mapv(|i| i as $t)
|
.mapv(|i| i as $t)
|
||||||
@@ -392,6 +408,7 @@ impl PyTransform {
|
|||||||
.into_dimensionality()
|
.into_dimensionality()
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.view(),
|
.view(),
|
||||||
|
&default_value,
|
||||||
)
|
)
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.mapv(|i| i > 0.5)
|
.mapv(|i| i > 0.5)
|
||||||
@@ -434,13 +451,19 @@ impl PyTransform {
|
|||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
point: PyArrayLike1<f64, AllowTypeChange>,
|
point: PyArrayLike1<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
center: PyArrayLike1<f64, AllowTypeChange>,
|
||||||
) -> Bound<'py, PyArray1<f64>> {
|
) -> Bound<'py, PyArray1<f64>> {
|
||||||
let point = point.as_array();
|
let point = point.as_array();
|
||||||
if let Some(slice) = point.as_slice() {
|
let center = center.as_array();
|
||||||
self.inner.transform_point(slice)
|
|
||||||
} else {
|
match (point.as_slice(), center.as_slice()) {
|
||||||
let point = point.to_vec();
|
(Some(point), Some(center)) => self.inner.transform_point(point, center),
|
||||||
self.inner.transform_point(&point)
|
(Some(point), None) => self.inner.transform_point(point, ¢er.to_vec()),
|
||||||
|
(None, Some(center)) => self.inner.transform_point(&point.to_vec(), center),
|
||||||
|
(None, None) => self
|
||||||
|
.inner
|
||||||
|
.transform_point(&point.to_vec(), ¢er.to_vec()),
|
||||||
}
|
}
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
}
|
}
|
||||||
@@ -450,10 +473,19 @@ impl PyTransform {
|
|||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
points: PyArrayLike2<f64, AllowTypeChange>,
|
points: PyArrayLike2<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
center: PyArrayLike1<f64, AllowTypeChange>,
|
||||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||||
let points = points.as_array();
|
let points = points.as_array();
|
||||||
|
let center = center.as_array();
|
||||||
Ok(py
|
Ok(py
|
||||||
.detach(|| self.inner.transform_points(points))
|
.detach(|| {
|
||||||
|
if let Some(center) = center.as_slice() {
|
||||||
|
self.inner.transform_points(points, center)
|
||||||
|
} else {
|
||||||
|
self.inner.transform_points(points, ¢er.to_vec())
|
||||||
|
}
|
||||||
|
})
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?
|
||||||
.into_pyarray(py))
|
.into_pyarray(py))
|
||||||
}
|
}
|
||||||
@@ -694,6 +726,29 @@ impl PyTransform {
|
|||||||
))),
|
))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn register_translation_fft<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
fixed: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
moving: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
let fixed = fixed.as_array();
|
||||||
|
let moving = moving.as_array();
|
||||||
|
if fixed.shape() != moving.shape() {
|
||||||
|
return Err(PyErr::from(Error::ShapeMismatch(
|
||||||
|
fixed.shape().to_vec(),
|
||||||
|
moving.shape().to_vec(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation_fft(fixed, moving).map_err(Error::from)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gen_stub_pyclass]
|
#[gen_stub_pyclass]
|
||||||
@@ -709,22 +764,375 @@ pub(crate) struct PyTransforms {
|
|||||||
pub(crate) inner: Transforms,
|
pub(crate) inner: Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pymethods]
|
||||||
|
#[pymethods]
|
||||||
|
impl PyView {
|
||||||
|
/// the bspline order used for interpolation when using transforms
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
#[getter]
|
||||||
|
fn get_interpolation_order(&self) -> Option<usize> {
|
||||||
|
self.view.transforms.interpolation_order
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bspline order used for interpolation when using transforms
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
#[setter]
|
||||||
|
fn set_interpolation_order(&mut self, order: Option<usize>) {
|
||||||
|
self.view.transforms.interpolation_order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bspline order used for interpolation when using transforms
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
fn with_interpolation_order(&self, order: Option<usize>) -> Self {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.view.transforms.interpolation_order = order;
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn get_default_interpolation_value(&self, channel: usize) -> PyResult<PyDefaultValue> {
|
||||||
|
let channel_name = self.view.channel_name(channel)?.to_string();
|
||||||
|
Ok(PyDefaultValue {
|
||||||
|
inner: self
|
||||||
|
.view
|
||||||
|
.transforms
|
||||||
|
.default_value
|
||||||
|
.get(&channel_name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn set_default_interpolation_value(
|
||||||
|
&mut self,
|
||||||
|
channel: usize,
|
||||||
|
#[gen_stub(override_type(type_repr="float | transforms.DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||||
|
value: Bound<PyAny>,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let channel_name = self.view.channel_name(channel)?.to_string();
|
||||||
|
if let Ok(value) = value.extract::<PyDefaultValue>() {
|
||||||
|
self.view
|
||||||
|
.transforms
|
||||||
|
.default_value
|
||||||
|
.insert(channel_name, value.inner);
|
||||||
|
} else if let Ok(value) = value.extract::<f64>() {
|
||||||
|
self.view
|
||||||
|
.transforms
|
||||||
|
.default_value
|
||||||
|
.insert(channel_name, value.into());
|
||||||
|
} else if let Ok(value) = value.try_iter() {
|
||||||
|
self.view.transforms.default_value.insert(
|
||||||
|
channel_name,
|
||||||
|
value
|
||||||
|
.map(|i| i?.extract::<f64>())
|
||||||
|
.collect::<PyResult<Vec<_>>>()?
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"cannot parse value: {:?} into DefaultValue",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn with_default_interpolation_value(
|
||||||
|
&self,
|
||||||
|
channel: usize,
|
||||||
|
#[gen_stub(override_type(type_repr="float | transforms.DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||||
|
value: Bound<PyAny>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.set_default_interpolation_value(channel, value)?;
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||||
|
/// backward compatibility
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||||
|
fn with_transform(
|
||||||
|
&self,
|
||||||
|
channels: bool,
|
||||||
|
drift: bool,
|
||||||
|
file: Option<PathBuf>,
|
||||||
|
bead_files: Option<Vec<PathBuf>>,
|
||||||
|
main_channel: Option<usize>,
|
||||||
|
default_transform: Option<Vec<f64>>,
|
||||||
|
) -> PyResult<PyView> {
|
||||||
|
self.with_transforms(
|
||||||
|
channels,
|
||||||
|
drift,
|
||||||
|
file,
|
||||||
|
bead_files,
|
||||||
|
main_channel,
|
||||||
|
default_transform,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns a view where channels and/or frames are registered with an affine transformation
|
||||||
|
// channels: True/False register channels using bead_files
|
||||||
|
// drift: True/False register frames to correct drift
|
||||||
|
// file: load registration from file with name file, default: transform.yml in self.path.parent
|
||||||
|
// bead_files: files used to register channels, default: files in self.path.parent,
|
||||||
|
// with names starting with 'beads'
|
||||||
|
// main channel: which channel to transform with default_transform
|
||||||
|
// default_transform: parameters of the default transform: [a, b, c, d, e, f] where the affine transform matrix is
|
||||||
|
// | a b e |
|
||||||
|
// | c d f |
|
||||||
|
// | 0 0 1 |
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||||
|
fn with_transforms(
|
||||||
|
&self,
|
||||||
|
channels: bool,
|
||||||
|
drift: bool,
|
||||||
|
file: Option<PathBuf>,
|
||||||
|
bead_files: Option<Vec<PathBuf>>,
|
||||||
|
main_channel: Option<usize>,
|
||||||
|
default_transform: Option<Vec<f64>>,
|
||||||
|
) -> PyResult<PyView> {
|
||||||
|
let mut new = self.clone();
|
||||||
|
let bead_files = bead_files
|
||||||
|
.as_ref()
|
||||||
|
.map(|i| i.iter().map(|j| j.as_path()).collect::<Vec<_>>());
|
||||||
|
new.view.transforms = Transforms::calculate_transforms_from(
|
||||||
|
file.as_deref(),
|
||||||
|
if channels {
|
||||||
|
bead_files.as_deref()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
if drift { Some(self.view.path()) } else { None },
|
||||||
|
main_channel,
|
||||||
|
default_transform,
|
||||||
|
)?;
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the transformation
|
||||||
|
#[getter]
|
||||||
|
fn get_transforms(&self) -> PyResult<PyTransforms> {
|
||||||
|
Ok(PyTransforms {
|
||||||
|
inner: self.view.get_transforms().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the transformation
|
||||||
|
#[setter]
|
||||||
|
fn set_transforms(&mut self, transform: PyTransforms) {
|
||||||
|
self.view.set_transforms(transform.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_transforms_from_yaml(&mut self, path: PathBuf) -> PyResult<()> {
|
||||||
|
self.view.load_transforms_from_yaml(path.as_path())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_channel_transforms_2d(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> PyResult<HashMap<String, PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_channel_transforms_2d(main_channel)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, t)| {
|
||||||
|
(
|
||||||
|
n,
|
||||||
|
PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_channel_transforms_3d(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> PyResult<HashMap<String, PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_channel_transforms_3d(main_channel)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, t)| {
|
||||||
|
(
|
||||||
|
n,
|
||||||
|
PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_drift_transforms_2d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_drift_transforms_2d()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_drift_transforms_3d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_drift_transforms_3d()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gen_stub_pymethods]
|
#[gen_stub_pymethods]
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyTransforms {
|
impl PyTransforms {
|
||||||
#[new]
|
#[new]
|
||||||
#[pyo3(signature = (channel = HashMap::new(), drift = Vec::new()))]
|
#[pyo3(signature = (channel = HashMap::new(), drift = Vec::new(), interpolation_order = None, default_value = None))]
|
||||||
fn new(
|
fn new(
|
||||||
channel: HashMap<String, PyTransform>,
|
channel: HashMap<String, PyTransform>,
|
||||||
drift: Vec<PyTransform>,
|
drift: Vec<PyTransform>,
|
||||||
|
interpolation_order: Option<usize>,
|
||||||
|
default_value: Option<HashMap<String, PyDefaultValue>>,
|
||||||
) -> PyResult<PyTransforms> {
|
) -> PyResult<PyTransforms> {
|
||||||
let channel = channel.into_iter().map(|(n, t)| (n, t.inner)).collect();
|
let channel = channel.into_iter().map(|(n, t)| (n, t.inner)).collect();
|
||||||
let drift = drift.into_iter().map(|t| t.inner).collect();
|
let drift = drift.into_iter().map(|t| t.inner).collect();
|
||||||
Ok(PyTransforms {
|
Ok(PyTransforms {
|
||||||
inner: Transforms { channel, drift },
|
inner: Transforms {
|
||||||
|
default_value: default_value
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|(k, v)| (k, v.inner))
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
interpolation_order,
|
||||||
|
channel,
|
||||||
|
drift,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn __getnewargs__(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
HashMap<String, PyTransform>,
|
||||||
|
Vec<PyTransform>,
|
||||||
|
Option<usize>,
|
||||||
|
HashMap<String, PyDefaultValue>,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
self.inner
|
||||||
|
.channel
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), PyTransform { inner: v.clone() }))
|
||||||
|
.collect(),
|
||||||
|
self.inner
|
||||||
|
.drift
|
||||||
|
.iter()
|
||||||
|
.map(|i| PyTransform { inner: i.clone() })
|
||||||
|
.collect(),
|
||||||
|
self.inner.interpolation_order,
|
||||||
|
self.inner
|
||||||
|
.default_value
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), PyDefaultValue { inner: v.clone() }))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bspline order used for interpolation when using transforms,
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
#[getter]
|
||||||
|
fn get_interpolation_order(&self) -> Option<usize> {
|
||||||
|
self.inner.interpolation_order
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bspline order used for interpolation when using transforms
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
#[setter]
|
||||||
|
fn set_interpolation_order(&mut self, order: Option<usize>) {
|
||||||
|
self.inner.interpolation_order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bspline order used for interpolation when using transforms
|
||||||
|
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||||
|
fn with_interpolation_order(&self, order: Option<usize>) -> Self {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.inner.interpolation_order = order;
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn get_default_interpolation_value(&self, channel_name: &str) -> PyResult<PyDefaultValue> {
|
||||||
|
Ok(PyDefaultValue {
|
||||||
|
inner: self
|
||||||
|
.inner
|
||||||
|
.default_value
|
||||||
|
.get(channel_name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn set_default_interpolation_value(
|
||||||
|
&mut self,
|
||||||
|
channel_name: String,
|
||||||
|
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||||
|
value: Bound<PyAny>,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
if let Ok(value) = value.extract::<PyDefaultValue>() {
|
||||||
|
self.inner.default_value.insert(channel_name, value.inner);
|
||||||
|
} else if let Ok(value) = value.extract::<f64>() {
|
||||||
|
self.inner.default_value.insert(channel_name, value.into());
|
||||||
|
} else if let Ok(value) = value.try_iter() {
|
||||||
|
self.inner.default_value.insert(
|
||||||
|
channel_name,
|
||||||
|
value
|
||||||
|
.map(|i| i?.extract::<f64>())
|
||||||
|
.collect::<PyResult<Vec<_>>>()?
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"cannot parse value: {:?} into DefaultValue",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the interpolated image outside the data
|
||||||
|
fn with_default_interpolation_value(
|
||||||
|
&self,
|
||||||
|
channel_name: String,
|
||||||
|
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||||
|
value: Bound<PyAny>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.set_default_interpolation_value(channel_name, value)?;
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn get_channel_transforms(&self) -> HashMap<String, PyTransform> {
|
fn get_channel_transforms(&self) -> HashMap<String, PyTransform> {
|
||||||
self.inner
|
self.inner
|
||||||
@@ -755,10 +1163,6 @@ impl PyTransforms {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getnewargs__(&self) -> (HashMap<String, PyTransform>, Vec<PyTransform>) {
|
|
||||||
(self.get_channel_transforms(), self.get_drift_transforms())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
pub(crate) fn load(path: PathBuf) -> PyResult<PyTransforms> {
|
pub(crate) fn load(path: PathBuf) -> PyResult<PyTransforms> {
|
||||||
Ok(PyTransforms {
|
Ok(PyTransforms {
|
||||||
|
|||||||
+54
-19
@@ -2,7 +2,7 @@ use crate::axes::{Axis, Shape};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use ndarray::{Array, Dimension, Ix2, Ix5, s};
|
use ndarray::{Array, Dimension, Ix2, Ix5, s};
|
||||||
use num::{FromPrimitive, Zero};
|
use num::{FromPrimitive, One, Zero};
|
||||||
use ome_metadata::Ome;
|
use ome_metadata::Ome;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -128,7 +128,7 @@ pub enum PixelType {
|
|||||||
I128,
|
I128,
|
||||||
/// unsigned 128-bit integer
|
/// unsigned 128-bit integer
|
||||||
U128,
|
U128,
|
||||||
/// 128-bit float (emulated)
|
/// 128-bit float (not supported)
|
||||||
F128,
|
F128,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +148,8 @@ impl PixelType {
|
|||||||
/// array data with a specific pixel type
|
/// array data with a specific pixel type
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ArrayT<D: Dimension> {
|
pub enum ArrayT<D: Dimension> {
|
||||||
|
/// boolean array
|
||||||
|
Bool(Array<bool, D>),
|
||||||
/// signed 8-bit integer array
|
/// signed 8-bit integer array
|
||||||
I8(Array<i8, D>),
|
I8(Array<i8, D>),
|
||||||
/// unsigned 8-bit integer array
|
/// unsigned 8-bit integer array
|
||||||
@@ -168,24 +170,28 @@ pub enum ArrayT<D: Dimension> {
|
|||||||
I64(Array<i64, D>),
|
I64(Array<i64, D>),
|
||||||
/// unsigned 64-bit integer array
|
/// unsigned 64-bit integer array
|
||||||
U64(Array<u64, D>),
|
U64(Array<u64, D>),
|
||||||
/// signed 128-bit integer array
|
/// signed 128-bit integer array (not supported by numpy)
|
||||||
I128(Array<i128, D>),
|
I128(Array<i128, D>),
|
||||||
/// unsigned 128-bit integer array
|
/// unsigned 128-bit integer array (not supported by numpy)
|
||||||
U128(Array<u128, D>),
|
U128(Array<u128, D>),
|
||||||
/// 128-bit float array (emulated as f64)
|
|
||||||
F128(Array<f64, D>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// type alias for a single frame (2D image)
|
/// type alias for a single frame (2D image)
|
||||||
pub type Frame = ArrayT<Ix2>;
|
pub type Frame = ArrayT<Ix2>;
|
||||||
|
|
||||||
/// trait for reading image files
|
/// trait for reading image files
|
||||||
pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
pub trait Reader: Clone + Sized + Debug + Sync + Send + Hash + Into<DynReader> {
|
||||||
/// create a new reader for the given path, series, and position
|
/// create a new reader for the given path, series, and position
|
||||||
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>;
|
P: AsRef<Path>;
|
||||||
|
|
||||||
|
/// a simple fast test to see if the reader can open a path without parsing the whole file
|
||||||
|
/// not a guarantee of success, but rather an early failure
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>;
|
||||||
|
|
||||||
/// the name of the reader type
|
/// the name of the reader type
|
||||||
fn reader_name(&self) -> &'static str {
|
fn reader_name(&self) -> &'static str {
|
||||||
type_name::<Self>()
|
type_name::<Self>()
|
||||||
@@ -197,9 +203,9 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
|||||||
|
|
||||||
/// get a sliceable view on the image file
|
/// get a sliceable view on the image file
|
||||||
fn view(&self) -> View<Ix5, Self> {
|
fn view(&self) -> View<Ix5, Self> {
|
||||||
let shape = self.shape();
|
let shape = self.raw_shape();
|
||||||
let slice = s![0..shape.c, 0..shape.z, 0..shape.t, 0..shape.y, 0..shape.x,];
|
let slice = s![0..shape.c, 0..shape.z, 0..shape.t, 0..shape.y, 0..shape.x,];
|
||||||
View::new(
|
View::new_with_slice_and_axes(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
slice.as_ref().to_vec(),
|
slice.as_ref().to_vec(),
|
||||||
vec![Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X],
|
vec![Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X],
|
||||||
@@ -216,7 +222,7 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
|||||||
/// the position index
|
/// the position index
|
||||||
fn position(&self) -> usize;
|
fn position(&self) -> usize;
|
||||||
/// the shape of the image data
|
/// the shape of the image data
|
||||||
fn shape(&self) -> &Shape;
|
fn raw_shape(&self) -> &Shape;
|
||||||
/// the pixel type of the image data
|
/// the pixel type of the image data
|
||||||
fn pixel_type(&self) -> &PixelType;
|
fn pixel_type(&self) -> &PixelType;
|
||||||
/// get all available positions for a given series
|
/// get all available positions for a given series
|
||||||
@@ -314,6 +320,7 @@ macro_rules! try_into_array_body {
|
|||||||
($self:expr, $map:ident) => {{
|
($self:expr, $map:ident) => {{
|
||||||
let mut err = Ok(());
|
let mut err = Ok(());
|
||||||
let arr = match $self {
|
let arr = match $self {
|
||||||
|
ArrayT::Bool(v) => v.$map(|x| if x { T::one() } else { T::zero() }),
|
||||||
ArrayT::I8(v) => v.$map(|x| {
|
ArrayT::I8(v) => v.$map(|x| {
|
||||||
T::from_i8(x).unwrap_or_else(|| {
|
T::from_i8(x).unwrap_or_else(|| {
|
||||||
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
@@ -356,7 +363,7 @@ macro_rules! try_into_array_body {
|
|||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
ArrayT::F64(v) | ArrayT::F128(v) => v.$map(|x| {
|
ArrayT::F64(v) => v.$map(|x| {
|
||||||
T::from_f64(x).unwrap_or_else(|| {
|
T::from_f64(x).unwrap_or_else(|| {
|
||||||
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
@@ -397,7 +404,7 @@ macro_rules! try_into_array_body {
|
|||||||
impl<D, T> TryInto<Array<T, D>> for ArrayT<D>
|
impl<D, T> TryInto<Array<T, D>> for ArrayT<D>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
T: FromPrimitive + Zero + 'static,
|
T: FromPrimitive + Zero + One + 'static,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -409,7 +416,7 @@ where
|
|||||||
impl<D, T> TryInto<Array<T, D>> for &ArrayT<D>
|
impl<D, T> TryInto<Array<T, D>> for &ArrayT<D>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
T: Clone + FromPrimitive + Zero + 'static,
|
T: Clone + FromPrimitive + Zero + One + 'static,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -475,6 +482,34 @@ impl Reader for DynReader {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut can_open = false;
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
{
|
||||||
|
can_open |= tiff::TiffReader::can_open(&path)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tiffseq")]
|
||||||
|
{
|
||||||
|
can_open |= tiffseq::TiffSeqReader::can_open(&path)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "czi")]
|
||||||
|
{
|
||||||
|
can_open |= czi::CziReader::can_open(&path)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "bioformats_rust")]
|
||||||
|
{
|
||||||
|
can_open |= bioformats_rust::BioFormatsRustReader::can_open(&path)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "bioformats_java")]
|
||||||
|
{
|
||||||
|
can_open |= bioformats_java::BioFormatsJavaReader::can_open(&path)?;
|
||||||
|
}
|
||||||
|
Ok(can_open)
|
||||||
|
}
|
||||||
|
|
||||||
fn reader_name(&self) -> &'static str {
|
fn reader_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "tiff")]
|
#[cfg(feature = "tiff")]
|
||||||
@@ -577,18 +612,18 @@ impl Reader for DynReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "tiff")]
|
#[cfg(feature = "tiff")]
|
||||||
DynReader::Tiff(r) => r.shape(),
|
DynReader::Tiff(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "tiffseq")]
|
#[cfg(feature = "tiffseq")]
|
||||||
DynReader::TiffSeq(r) => r.shape(),
|
DynReader::TiffSeq(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "czi")]
|
#[cfg(feature = "czi")]
|
||||||
DynReader::Czi(r) => r.shape(),
|
DynReader::Czi(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "bioformats_rust")]
|
#[cfg(feature = "bioformats_rust")]
|
||||||
DynReader::BioFormatsRust(r) => r.shape(),
|
DynReader::BioFormatsRust(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.shape(),
|
DynReader::BioFormatsJava(r) => r.raw_shape(),
|
||||||
#[expect(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ static JVM_PID: AtomicU32 = AtomicU32::new(0);
|
|||||||
/// else -- verified by `JNI_GetCreatedJavaVMs`, which still reports 1 in the child, so a second
|
/// else -- verified by `JNI_GetCreatedJavaVMs`, which still reports 1 in the child, so a second
|
||||||
/// `JNI_CreateJavaVM` (which is what j4rs' `Jvm::attach_thread` calls) takes the already-exists
|
/// `JNI_CreateJavaVM` (which is what j4rs' `Jvm::attach_thread` calls) takes the already-exists
|
||||||
/// path. An `exec` replaces the address space, and that does reset the count to 0.
|
/// path. An `exec` replaces the address space, and that does reset the count to 0.
|
||||||
fn check_not_forked() -> Result<(), Error> {
|
pub fn check_not_forked() -> Result<(), Error> {
|
||||||
let built_in = JVM_PID.load(Ordering::Relaxed);
|
let built_in = JVM_PID.load(Ordering::Relaxed);
|
||||||
if built_in != 0 && built_in != std::process::id() {
|
if built_in != 0 && built_in != std::process::id() {
|
||||||
return Err(Error::ForkedJvm(built_in, std::process::id()));
|
return Err(Error::ForkedJvm(built_in, std::process::id()));
|
||||||
@@ -521,7 +521,7 @@ impl BioFormatsJavaReader {
|
|||||||
PixelType::U64 => 8,
|
PixelType::U64 => 8,
|
||||||
PixelType::I128 => 16,
|
PixelType::I128 => 16,
|
||||||
PixelType::U128 => 16,
|
PixelType::U128 => 16,
|
||||||
PixelType::F128 => 8,
|
PixelType::F128 => 16,
|
||||||
};
|
};
|
||||||
Ok(bytes
|
Ok(bytes
|
||||||
.chunks(chunk_size)
|
.chunks(chunk_size)
|
||||||
@@ -587,7 +587,7 @@ impl BioFormatsJavaReader {
|
|||||||
(PixelType::U64, true) => get_frame!(u64, <8),
|
(PixelType::U64, true) => get_frame!(u64, <8),
|
||||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
(PixelType::F128, true) => Err(Error::F128NotSupported),
|
||||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||||
@@ -601,7 +601,7 @@ impl BioFormatsJavaReader {
|
|||||||
(PixelType::U64, false) => get_frame!(u64, >8),
|
(PixelType::U64, false) => get_frame!(u64, >8),
|
||||||
(PixelType::I128, false) => get_frame!(i128, >16),
|
(PixelType::I128, false) => get_frame!(i128, >16),
|
||||||
(PixelType::U128, false) => get_frame!(u128, >16),
|
(PixelType::U128, false) => get_frame!(u128, >16),
|
||||||
(PixelType::F128, false) => get_frame!(f64, >8),
|
(PixelType::F128, false) => Err(Error::F128NotSupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,6 +666,13 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(_path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Ome, Error> {
|
fn metadata(&self) -> Result<Ome, Error> {
|
||||||
self.get_ome()
|
self.get_ome()
|
||||||
}
|
}
|
||||||
@@ -698,7 +705,7 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ impl BioFormatsRustReader {
|
|||||||
PixelType::U64 => 8,
|
PixelType::U64 => 8,
|
||||||
PixelType::I128 => 16,
|
PixelType::I128 => 16,
|
||||||
PixelType::U128 => 16,
|
PixelType::U128 => 16,
|
||||||
PixelType::F128 => 8,
|
PixelType::F128 => 16,
|
||||||
};
|
};
|
||||||
Ok(bytes
|
Ok(bytes
|
||||||
.chunks(chunk_size)
|
.chunks(chunk_size)
|
||||||
@@ -206,7 +206,7 @@ impl BioFormatsRustReader {
|
|||||||
(PixelType::U64, true) => get_frame!(u64, <8),
|
(PixelType::U64, true) => get_frame!(u64, <8),
|
||||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
(PixelType::F128, true) => Err(Error::F128NotSupported),
|
||||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||||
@@ -220,7 +220,7 @@ impl BioFormatsRustReader {
|
|||||||
(PixelType::U64, false) => get_frame!(u64, >8),
|
(PixelType::U64, false) => get_frame!(u64, >8),
|
||||||
(PixelType::I128, false) => get_frame!(i128, >16),
|
(PixelType::I128, false) => get_frame!(i128, >16),
|
||||||
(PixelType::U128, false) => get_frame!(u128, >16),
|
(PixelType::U128, false) => get_frame!(u128, >16),
|
||||||
(PixelType::F128, false) => get_frame!(f64, >8),
|
(PixelType::F128, false) => Err(Error::F128NotSupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,6 +274,13 @@ impl Reader for BioFormatsRustReader {
|
|||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
Ok(path.as_ref().extension() != Some(std::ffi::OsStr::new("czi")))
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Ome, Error> {
|
fn metadata(&self) -> Result<Ome, Error> {
|
||||||
let reader = self.get_reader()?;
|
let reader = self.get_reader()?;
|
||||||
let metadata = reader.metadata();
|
let metadata = reader.metadata();
|
||||||
@@ -309,7 +316,7 @@ impl Reader for BioFormatsRustReader {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,14 +122,6 @@ impl From<Option<String>> for Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// errors specific to czi file reading
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum CziError {
|
|
||||||
/// czi file has no valid blocks
|
|
||||||
#[error("czi file has no valid blocks")]
|
|
||||||
NoValidBlocks,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Reader for CziReader {
|
impl Reader for CziReader {
|
||||||
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
@@ -195,7 +187,7 @@ impl Reader for CziReader {
|
|||||||
let mut max_y = i32::MIN;
|
let mut max_y = i32::MIN;
|
||||||
for &i in Ok(block_map.get(&(0, 0, 0)))
|
for &i in Ok(block_map.get(&(0, 0, 0)))
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_else(|| block_map.values().next().ok_or(CziError::NoValidBlocks))?
|
.unwrap_or_else(|| block_map.values().next().ok_or(Error::CziNoValidBlocks))?
|
||||||
{
|
{
|
||||||
if let Ok(info) = reader.try_get_sub_block_info_for_index(i) {
|
if let Ok(info) = reader.try_get_sub_block_info_for_index(i) {
|
||||||
let rect = info.get_logical_rect();
|
let rect = info.get_logical_rect();
|
||||||
@@ -219,10 +211,17 @@ impl Reader for CziReader {
|
|||||||
new.shape.x = (max_y - min_y) as usize;
|
new.shape.x = (max_y - min_y) as usize;
|
||||||
new.shape.y = (max_x - min_x) as usize;
|
new.shape.y = (max_x - min_x) as usize;
|
||||||
new.block_map = block_map;
|
new.block_map = block_map;
|
||||||
new.pixel_type = pixel_type.ok_or(CziError::NoValidBlocks)?;
|
new.pixel_type = pixel_type.ok_or(Error::CziNoValidBlocks)?;
|
||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
Ok(path.as_ref().extension() == Some(std::ffi::OsStr::new("czi")))
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Ome, Error> {
|
fn metadata(&self) -> Result<Ome, Error> {
|
||||||
let mut ome = Ome::default();
|
let mut ome = Ome::default();
|
||||||
let xml = xmltree::Element::parse(self.metadata_xml()?.as_bytes())?;
|
let xml = xmltree::Element::parse(self.metadata_xml()?.as_bytes())?;
|
||||||
@@ -1133,7 +1132,7 @@ impl Reader for CziReader {
|
|||||||
PixelType::U64 => get_frame!(u64, 8),
|
PixelType::U64 => get_frame!(u64, 8),
|
||||||
PixelType::I128 => get_frame!(i128, 16),
|
PixelType::I128 => get_frame!(i128, 16),
|
||||||
PixelType::U128 => get_frame!(u128, 16),
|
PixelType::U128 => get_frame!(u128, 16),
|
||||||
PixelType::F128 => get_frame!(f64, 8),
|
PixelType::F128 => Err(Error::F128NotSupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1149,7 +1148,7 @@ impl Reader for CziReader {
|
|||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,18 @@ impl Reader for TiffReader {
|
|||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
== 2;
|
== 2;
|
||||||
|
|
||||||
|
let image_description = reader.current_ifd().get_tag(Tag::ImageDescription);
|
||||||
|
let p_ndim = if n_samples > 1 { 3u8 } else { 2u8 };
|
||||||
|
let (size_c, size_t, size_z) = if let Ok(Value::Ascii(xml)) = image_description
|
||||||
|
&& xml.starts_with("<?xml")
|
||||||
|
{
|
||||||
|
let ome = Ome::from_xml(xml)?;
|
||||||
|
(
|
||||||
|
ome.image[0].pixels.size_c as usize,
|
||||||
|
ome.image[0].pixels.size_t as usize,
|
||||||
|
ome.image[0].pixels.size_z as usize,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
let imagej_bytes = reader
|
let imagej_bytes = reader
|
||||||
.current_ifd()
|
.current_ifd()
|
||||||
.get_tag(Tag::Unknown(50839))
|
.get_tag(Tag::Unknown(50839))
|
||||||
@@ -166,7 +178,7 @@ impl Reader for TiffReader {
|
|||||||
};
|
};
|
||||||
let metadata_str = String::from_utf8_lossy(&imagej_bytes);
|
let metadata_str = String::from_utf8_lossy(&imagej_bytes);
|
||||||
let metadata_map = parse_imagej_metadata(&metadata_str);
|
let metadata_map = parse_imagej_metadata(&metadata_str);
|
||||||
let p_ndim = if n_samples > 1 { 3u8 } else { 2u8 };
|
|
||||||
let size_c = if p_ndim == 3 {
|
let size_c = if p_ndim == 3 {
|
||||||
n_samples
|
n_samples
|
||||||
} else {
|
} else {
|
||||||
@@ -198,6 +210,8 @@ impl Reader for TiffReader {
|
|||||||
} else {
|
} else {
|
||||||
size_z
|
size_z
|
||||||
};
|
};
|
||||||
|
(size_c, size_t, size_z)
|
||||||
|
};
|
||||||
drop(reader);
|
drop(reader);
|
||||||
|
|
||||||
new.shape = Shape {
|
new.shape = Shape {
|
||||||
@@ -218,8 +232,32 @@ impl Reader for TiffReader {
|
|||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
let mut sig = [0; 2];
|
||||||
|
file.read_exact(&mut sig)?;
|
||||||
|
if sig == [73, 73] {
|
||||||
|
file.read_exact(&mut sig)?;
|
||||||
|
Ok((sig[0] == 42) || (sig[0] == 43))
|
||||||
|
} else if sig == [77, 77] {
|
||||||
|
Ok((sig[1] == 42) || (sig[1] == 43))
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Ome, Error> {
|
fn metadata(&self) -> Result<Ome, Error> {
|
||||||
let mut reader = self.get_reader()?;
|
let mut reader = self.get_reader()?;
|
||||||
|
let image_description = reader.current_ifd().get_tag(Tag::ImageDescription);
|
||||||
|
if let Ok(Value::Ascii(xml)) = image_description
|
||||||
|
&& xml.starts_with("<?xml")
|
||||||
|
{
|
||||||
|
Ok(Ome::from_xml(xml)?)
|
||||||
|
} else {
|
||||||
let imagej_bytes = reader
|
let imagej_bytes = reader
|
||||||
.current_ifd()
|
.current_ifd()
|
||||||
.get_tag(Tag::Unknown(50839))
|
.get_tag(Tag::Unknown(50839))
|
||||||
@@ -354,6 +392,7 @@ impl Reader for TiffReader {
|
|||||||
});
|
});
|
||||||
Ok(ome)
|
Ok(ome)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
|
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
|
||||||
let (page_idx, offset, stride) = if self.p_ndim == 3 {
|
let (page_idx, offset, stride) = if self.p_ndim == 3 {
|
||||||
@@ -438,25 +477,43 @@ impl Reader for TiffReader {
|
|||||||
fn position(&self) -> usize {
|
fn position(&self) -> usize {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
fn pixel_type(&self) -> &PixelType {
|
fn pixel_type(&self) -> &PixelType {
|
||||||
&self.pixel_type
|
&self.pixel_type
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_available_positions<P>(_path: P, _series: usize) -> Result<HashSet<usize>, Error>
|
fn get_available_positions<P>(path: P, _series: usize) -> Result<HashSet<usize>, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
if Self::can_open(path)? {
|
||||||
Ok(HashSet::from([0]))
|
Ok(HashSet::from([0]))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidReader(
|
||||||
|
"TiffReader".to_string(),
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
"it is not a tiff".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_available_series<P>(_path: P) -> Result<HashSet<usize>, Error>
|
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
if Self::can_open(path)? {
|
||||||
Ok(HashSet::from([0]))
|
Ok(HashSet::from([0]))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidReader(
|
||||||
|
"TiffReader".to_string(),
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
"it is not a tiff".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +578,8 @@ mod tests {
|
|||||||
metadata_e: "tiff/1xp53-01-AP1.tiff",
|
metadata_e: "tiff/1xp53-01-AP1.tiff",
|
||||||
metadata_f: "tiff/test.tif",
|
metadata_f: "tiff/test.tif",
|
||||||
metadata_g: "tiff/YTL1849A111_2023_05_04__14_46_19_cellnr_1_track.tif",
|
metadata_g: "tiff/YTL1849A111_2023_05_04__14_46_19_cellnr_1_track.tif",
|
||||||
metadata_h: "tiff/Wapl-AID_C46_13_tagRFPt_MCP_well1_video1_1_max.tif"
|
metadata_h: "tiff/Wapl-AID_C46_13_tagRFPt_MCP_well1_video1_1_max.tif",
|
||||||
|
metadata_i: "tiff/R1_NRF1_Gtf3a500nM_REAL_20260731_25pct_500ms_001_MMStack_PosR1_NRF1_Gtf3a500nM_REAL_20260731_25pct_500ms_001.ome.tif"
|
||||||
|
metadata_j: "tiff/20260922_Pos0_max.tif"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +239,13 @@ impl Reader for TiffSeqReader {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_open<P>(path: P) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
Ok(path.as_ref().is_dir())
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Ome, Error> {
|
fn metadata(&self) -> Result<Ome, Error> {
|
||||||
let mut ome = Ome::default();
|
let mut ome = Ome::default();
|
||||||
let info = self.metadata_map.get("Info").and_then(|v| v.as_mapping());
|
let info = self.metadata_map.get("Info").and_then(|v| v.as_mapping());
|
||||||
@@ -572,7 +579,7 @@ impl Reader for TiffSeqReader {
|
|||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,11 +587,20 @@ impl Reader for TiffSeqReader {
|
|||||||
&self.pixel_type
|
&self.pixel_type
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_available_positions<P>(_path: P, _series: usize) -> Result<HashSet<usize>, Error>
|
fn get_available_positions<P>(path: P, _series: usize) -> Result<HashSet<usize>, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
if Self::can_open(path)? {
|
||||||
Ok(HashSet::from([0]))
|
Ok(HashSet::from([0]))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidReader(
|
||||||
|
"TiffSeqReader".to_string(),
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
"not a tiff sequence".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
|
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ where
|
|||||||
PixelType::U64 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
PixelType::U64 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||||
PixelType::I128 => self.save_as_tiff_with_type::<i64, P>(path, options)?,
|
PixelType::I128 => self.save_as_tiff_with_type::<i64, P>(path, options)?,
|
||||||
PixelType::U128 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
PixelType::U128 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||||
PixelType::F128 => self.save_as_tiff_with_type::<f64, P>(path, options)?,
|
PixelType::F128 => Err(Error::F128NotSupported)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+600
-74
@@ -1,13 +1,19 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::readers::{DynReader, Reader};
|
use crate::readers::{DynReader, Frame, PixelType, Reader};
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
pub use image_registration::transform::Transform;
|
use image_registration::{bspline::DefaultValue, filter, transform::Transform};
|
||||||
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, s};
|
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, array, concatenate, s};
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::Seek;
|
use std::io::Seek;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tiffwrite::IJTiffFile;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
struct TransformOld {
|
struct TransformOld {
|
||||||
@@ -26,16 +32,20 @@ impl TryFrom<HashMap<String, TransformOld>> for Transforms {
|
|||||||
|
|
||||||
fn try_from(t: HashMap<String, TransformOld>) -> Result<Self, Error> {
|
fn try_from(t: HashMap<String, TransformOld>) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
default_value: Default::default(),
|
||||||
|
interpolation_order: None,
|
||||||
channel: t
|
channel: t
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(n, i)| {
|
.map(|(n, i)| {
|
||||||
|
let center = i
|
||||||
|
.size
|
||||||
|
.iter()
|
||||||
|
.zip(i.center_of_rotation_point.iter())
|
||||||
|
.map(|(s, c)| c - s / 2.0 + 0.5)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
Ok((
|
Ok((
|
||||||
n,
|
n,
|
||||||
Transform::new_with_center(
|
Transform::new_around_center(i.transform_parameters, ¢er)
|
||||||
i.transform_parameters,
|
|
||||||
i.center_of_rotation_point,
|
|
||||||
i.size.into_iter().map(|j| j as usize).collect(),
|
|
||||||
)
|
|
||||||
.with_dparameters(i.dtransform_parameters)
|
.with_dparameters(i.dtransform_parameters)
|
||||||
.inverse()?,
|
.inverse()?,
|
||||||
))
|
))
|
||||||
@@ -46,14 +56,40 @@ impl TryFrom<HashMap<String, TransformOld>> for Transforms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type DefaultValues = HashMap<String, DefaultValue>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
||||||
pub struct Transforms {
|
pub struct Transforms {
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) interpolation_order: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) default_value: DefaultValues,
|
||||||
|
/// a transform for each channel by channel name
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub channel: HashMap<String, Transform<IxDyn>>,
|
pub channel: HashMap<String, Transform<IxDyn>>,
|
||||||
|
/// a transform for each time point
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub drift: Vec<Transform<IxDyn>>,
|
pub drift: Vec<Transform<IxDyn>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Transforms {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
let mut keys: Vec<_> = self.channel.keys().collect();
|
||||||
|
keys.sort();
|
||||||
|
for key in keys {
|
||||||
|
key.hash(state);
|
||||||
|
self.channel[key].hash(state);
|
||||||
|
}
|
||||||
|
self.drift.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<Transforms> for &Transforms {
|
||||||
|
fn eq(&self, other: &Transforms) -> bool {
|
||||||
|
**self == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for Transforms {
|
impl Serialize for Transforms {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@@ -69,6 +105,8 @@ impl Serialize for Transforms {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Binary<'a> {
|
struct Binary<'a> {
|
||||||
|
interpolation_order: Option<usize>,
|
||||||
|
default_value: &'a DefaultValues,
|
||||||
channel: &'a HashMap<String, Transform<IxDyn>>,
|
channel: &'a HashMap<String, Transform<IxDyn>>,
|
||||||
drift: &'a Vec<Transform<IxDyn>>,
|
drift: &'a Vec<Transform<IxDyn>>,
|
||||||
}
|
}
|
||||||
@@ -81,6 +119,8 @@ impl Serialize for Transforms {
|
|||||||
.serialize(serializer)
|
.serialize(serializer)
|
||||||
} else {
|
} else {
|
||||||
Binary {
|
Binary {
|
||||||
|
interpolation_order: self.interpolation_order,
|
||||||
|
default_value: &self.default_value,
|
||||||
channel: &self.channel,
|
channel: &self.channel,
|
||||||
drift: &self.drift,
|
drift: &self.drift,
|
||||||
}
|
}
|
||||||
@@ -89,7 +129,31 @@ impl Serialize for Transforms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the kind of corrections the transforms are meant to correct for
|
||||||
|
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||||
|
pub enum TransformKind {
|
||||||
|
/// no tranforms
|
||||||
|
None,
|
||||||
|
/// correct warp between channels, ex: camera/filter misalignment, chromatic aberrations
|
||||||
|
Channel,
|
||||||
|
/// drift in the miscrocope between time points
|
||||||
|
Drift,
|
||||||
|
/// both warp between channels and drift
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
impl Transforms {
|
impl Transforms {
|
||||||
|
/// the kind of corrections the transforms are meant to correct for
|
||||||
|
pub fn kind(&self) -> TransformKind {
|
||||||
|
match (self.channel.is_empty(), self.drift.is_empty()) {
|
||||||
|
(true, true) => TransformKind::None,
|
||||||
|
(false, true) => TransformKind::Channel,
|
||||||
|
(true, false) => TransformKind::Drift,
|
||||||
|
(false, false) => TransformKind::Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// load the transforms from a yml file
|
||||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||||
let mut file = std::fs::File::open(&path)?;
|
let mut file = std::fs::File::open(&path)?;
|
||||||
Ok(
|
Ok(
|
||||||
@@ -104,6 +168,7 @@ impl Transforms {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// save the transforms to a yml file
|
||||||
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||||
let file = std::fs::OpenOptions::new()
|
let file = std::fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
@@ -113,57 +178,164 @@ impl Transforms {
|
|||||||
Ok(yaml_serde::to_writer(file, self)?)
|
Ok(yaml_serde::to_writer(file, self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_channel_transforms_2d(
|
/// helper function to eiter load transforms from file or calculate and save them
|
||||||
bead_files: &[&Path],
|
pub fn calculate_transforms_from<P, Q, R>(
|
||||||
|
transform_file: Option<P>,
|
||||||
|
bead_files: Option<&[Q]>,
|
||||||
|
drift_file: Option<R>,
|
||||||
|
main_channel: Option<usize>,
|
||||||
|
default_transform: Option<Vec<f64>>,
|
||||||
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
Q: AsRef<Path>,
|
||||||
|
R: AsRef<Path>,
|
||||||
|
{
|
||||||
|
if let Some(transform_file) = transform_file.as_ref()
|
||||||
|
&& let Ok(transforms) = Self::load(transform_file.as_ref().with_extension("yml"))
|
||||||
|
{
|
||||||
|
Ok(transforms)
|
||||||
|
} else {
|
||||||
|
let default_transform = default_transform.map(Transform::new);
|
||||||
|
let mut transforms = Self::default();
|
||||||
|
if let Some(bead_files) = bead_files {
|
||||||
|
let main_channel = main_channel.unwrap_or(0);
|
||||||
|
transforms.channel = Self::calculate_channel_transforms_2d(
|
||||||
|
bead_files,
|
||||||
|
main_channel,
|
||||||
|
default_transform,
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, t)| (k, t.into_dyn()))
|
||||||
|
.collect();
|
||||||
|
};
|
||||||
|
if let Some(drift_file) = drift_file {
|
||||||
|
transforms.drift = Self::calculate_drift_transforms_2d(drift_file)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.into_dyn())
|
||||||
|
.collect();
|
||||||
|
};
|
||||||
|
if let Some(transform_file) = transform_file {
|
||||||
|
let transform_file = transform_file.as_ref();
|
||||||
|
transforms.save(transform_file.with_extension("yml"))?;
|
||||||
|
if let Some(bead_files) = bead_files {
|
||||||
|
transforms.save_channel_transform_tiff(
|
||||||
|
transform_file.with_extension("tif"),
|
||||||
|
bead_files,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(transforms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save a tiff file with the uncorrected and corrected frames of the bead files side by side
|
||||||
|
pub fn save_channel_transform_tiff<P, Q>(&self, file: P, bead_files: &[Q]) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
Q: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut transforms = self.clone();
|
||||||
|
transforms.drift = Vec::new();
|
||||||
|
let mut tif = IJTiffFile::new(file)?;
|
||||||
|
for (t, bead_file) in bead_files.iter().enumerate() {
|
||||||
|
let mut view = View::<_, DynReader>::from_path(bead_file)?.into_dyn();
|
||||||
|
let axes = view.axes();
|
||||||
|
if axes.contains(&Axis::Z) {
|
||||||
|
view = view.max_proj(Axis::Z)?;
|
||||||
|
}
|
||||||
|
if axes.contains(&Axis::T) {
|
||||||
|
view = view.max_proj(Axis::T)?;
|
||||||
|
}
|
||||||
|
let view_u = view.into_dimensionality::<Ix3>()?;
|
||||||
|
let view_t = view_u.with_transforms(transforms.clone());
|
||||||
|
for c in 0..view_u.shape().c {
|
||||||
|
let a = view_u.slice_cztyx(s![c, 0, 0, .., ..])?.as_array::<u16>()?;
|
||||||
|
let b = view_t.slice_cztyx(s![c, 0, 0, .., ..])?.as_array::<u16>()?;
|
||||||
|
let frame = concatenate(ndarray::Axis(1), &[a.view(), b.view()])?;
|
||||||
|
tif.save(frame, c, 0, t)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// calculate drift transforms in yx using image file
|
||||||
|
pub fn calculate_drift_transforms_2d<P: AsRef<Path>>(
|
||||||
|
file: P,
|
||||||
|
) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||||
|
View::<_, DynReader>::from_path(file)?.calculate_drift_transforms_2d()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// calculate drift transforms in zyx using image file
|
||||||
|
pub fn calculate_drift_transforms_3d<P: AsRef<Path>>(
|
||||||
|
file: P,
|
||||||
|
) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||||
|
View::<_, DynReader>::from_path(file)?.calculate_drift_transforms_3d()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// calculate channel transforms in yx using image file
|
||||||
|
pub fn calculate_channel_transforms_2d<P: AsRef<Path>>(
|
||||||
|
bead_files: &[P],
|
||||||
main_channel: usize,
|
main_channel: usize,
|
||||||
default_transform: Option<Transform<Ix2>>,
|
default_transform: Option<Transform<Ix2>>,
|
||||||
) -> Result<HashMap<String, Transform<Ix2>>, Error> {
|
) -> Result<HashMap<String, Transform<Ix2>>, Error> {
|
||||||
|
// TODO: autodetect main channel
|
||||||
let mut transforms = Vec::new();
|
let mut transforms = Vec::new();
|
||||||
let default_transform = default_transform.unwrap_or_default();
|
let default_transform = default_transform.unwrap_or_default();
|
||||||
for file in bead_files {
|
for file in bead_files {
|
||||||
let view = View::<_, DynReader>::from_path(file)?;
|
let view = View::<_, DynReader>::from_path(file)?;
|
||||||
transforms.push(view.calculate_channel_transforms_2d(main_channel)?)
|
transforms.push(view.calculate_channel_transforms_2d(main_channel)?)
|
||||||
}
|
}
|
||||||
let channels = transforms
|
let transforms = transforms.into_iter().flat_map(|map| map.into_iter()).fold(
|
||||||
.iter()
|
HashMap::new(),
|
||||||
.flat_map(|t| t.keys())
|
|mut acc, (channel, transform)| {
|
||||||
.collect::<HashSet<_>>();
|
acc.entry(channel)
|
||||||
let mut average_transforms = HashMap::new();
|
.or_insert_with(Vec::new)
|
||||||
for channel in channels {
|
.push(transform * &default_transform);
|
||||||
let (s, n) = transforms
|
acc
|
||||||
.iter()
|
},
|
||||||
.filter_map(|t| {
|
|
||||||
if t.contains_key(channel) {
|
|
||||||
Some((&t[channel] * &default_transform).matrix())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
|
||||||
let matrix = s / (n as f64);
|
|
||||||
let (s, n) = transforms
|
|
||||||
.iter()
|
|
||||||
.filter_map(|t| {
|
|
||||||
if t.contains_key(channel) {
|
|
||||||
Some(((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
|
||||||
let dmatrix = (s / n as f64).sqrt();
|
|
||||||
average_transforms.insert(
|
|
||||||
channel.clone(),
|
|
||||||
Transform::default()
|
|
||||||
.with_matrix(matrix.view())
|
|
||||||
.with_dmatrix(dmatrix.view()),
|
|
||||||
);
|
);
|
||||||
|
Ok(transforms
|
||||||
|
.iter()
|
||||||
|
.map(|(channel, ts)| {
|
||||||
|
let n = ts.len();
|
||||||
|
let parameters = ts
|
||||||
|
.iter()
|
||||||
|
.fold(vec![0.0; 6], |mut acc, t| {
|
||||||
|
for (a, p) in acc.iter_mut().zip(&t.parameters) {
|
||||||
|
*a += p;
|
||||||
}
|
}
|
||||||
Ok(average_transforms)
|
acc
|
||||||
|
})
|
||||||
|
.iter()
|
||||||
|
.map(|i| i / n as f64)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let dparameters = if n == 1 {
|
||||||
|
ts[0].dparameters.clone()
|
||||||
|
} else {
|
||||||
|
ts.iter()
|
||||||
|
.fold(vec![0.0; 6], |mut acc, t| {
|
||||||
|
for ((a, p), dp) in acc.iter_mut().zip(¶meters).zip(&t.dparameters)
|
||||||
|
{
|
||||||
|
*a += (dp - p).powi(2);
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
.iter()
|
||||||
|
.map(|i| (i / n as f64).sqrt())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
(
|
||||||
|
channel.to_string(),
|
||||||
|
Transform::new(parameters).with_dparameters(dparameters),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_channel_transforms_3d(
|
/// calculate channel transforms in zyx using image file
|
||||||
bead_files: &[&Path],
|
pub fn calculate_channel_transforms_3d<P: AsRef<Path>>(
|
||||||
|
bead_files: &[P],
|
||||||
main_channel: usize,
|
main_channel: usize,
|
||||||
default_transform: Option<Transform<Ix3>>,
|
default_transform: Option<Transform<Ix3>>,
|
||||||
) -> Result<HashMap<String, Transform<Ix3>>, Error> {
|
) -> Result<HashMap<String, Transform<Ix3>>, Error> {
|
||||||
@@ -210,29 +382,56 @@ impl Transforms {
|
|||||||
}
|
}
|
||||||
Ok(average_transforms)
|
Ok(average_transforms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get the combination of channel and drift transforms for a certain channel and time point
|
||||||
|
pub fn get_transform(
|
||||||
|
&self,
|
||||||
|
channel: &str,
|
||||||
|
t: usize,
|
||||||
|
) -> Result<Option<Cow<'_, Transform<IxDyn>>>, Error> {
|
||||||
|
match (self.channel.get(channel), self.drift.get(t)) {
|
||||||
|
(Some(channel), Some(drift)) => match (channel.ndim(), drift.ndim()) {
|
||||||
|
(2, 2) | (3, 3) => Ok(Some(Cow::Owned(drift * channel))),
|
||||||
|
(2, 3) => Ok(Some(Cow::Owned(drift.add_dimension(0) * channel))),
|
||||||
|
(3, 2) => Ok(Some(Cow::Owned(drift * channel.add_dimension(0)))),
|
||||||
|
(2 | 3, n) | (n, _) => Err(Error::TransformationNotImplemented(n)),
|
||||||
|
},
|
||||||
|
(Some(channel), None) => Ok(Some(Cow::Borrowed(channel))),
|
||||||
|
(None, Some(drift)) => Ok(Some(Cow::Borrowed(drift))),
|
||||||
|
(None, None) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Dimension, R: Reader> View<D, R> {
|
impl<D: Dimension, R: Reader> View<D, R> {
|
||||||
#[cfg(feature = "transforms")]
|
pub fn get_transforms(&self) -> &Transforms {
|
||||||
pub(crate) fn get_transforms(&self) -> &Transforms {
|
|
||||||
&self.transforms
|
&self.transforms
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
pub fn set_transforms(&mut self, transforms: Transforms) {
|
||||||
pub(crate) fn set_transforms(&mut self, transforms: Transforms) {
|
|
||||||
self.transforms = transforms;
|
self.transforms = transforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// apply transforms to a new view
|
||||||
|
pub fn with_transforms(&self, transforms: Transforms) -> Self {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.transforms = transforms;
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
/// load the transforms from a yml file and apply to a new view
|
||||||
pub fn with_transforms_from_yaml(mut self, path: &Path) -> Result<Self, Error> {
|
pub fn with_transforms_from_yaml(mut self, path: &Path) -> Result<Self, Error> {
|
||||||
self.set_transforms(Transforms::load(path)?);
|
self.set_transforms(Transforms::load(path)?);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// load the transforms from a yml file and apply to the view
|
||||||
pub fn load_transforms_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
pub fn load_transforms_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
||||||
self.set_transforms(Transforms::load(path)?);
|
self.set_transforms(Transforms::load(path)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// calculate channel transforms in yx using this image view
|
||||||
pub fn calculate_channel_transforms_2d(
|
pub fn calculate_channel_transforms_2d(
|
||||||
&self,
|
&self,
|
||||||
main_channel: usize,
|
main_channel: usize,
|
||||||
@@ -242,23 +441,13 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
.max_proj(Axis::Z)?
|
.max_proj(Axis::Z)?
|
||||||
.as_array::<f64>()?;
|
.as_array::<f64>()?;
|
||||||
let mut transforms = HashMap::new();
|
let mut transforms = HashMap::new();
|
||||||
let shape = vec![self.shape().y, self.shape().x];
|
|
||||||
let center = shape
|
|
||||||
.iter()
|
|
||||||
.map(|&i| (i as f64 - 1.0) / 2.0)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
||||||
let name = metadata
|
let name = metadata
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| format!("channel{}", channel));
|
.unwrap_or_else(|| format!("channel{}", channel));
|
||||||
if channel == main_channel {
|
if channel == main_channel {
|
||||||
transforms.insert(
|
transforms.insert(name, Transform::default());
|
||||||
name,
|
|
||||||
Transform::default()
|
|
||||||
.with_shape(shape.clone())
|
|
||||||
.with_center(center.clone()),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let max = self
|
let max = self
|
||||||
.slice_cztyx(s![channel, .., 0, .., ..])?
|
.slice_cztyx(s![channel, .., 0, .., ..])?
|
||||||
@@ -273,6 +462,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
Ok(transforms)
|
Ok(transforms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// calculate channel transforms in zyx using this image view
|
||||||
pub fn calculate_channel_transforms_3d(
|
pub fn calculate_channel_transforms_3d(
|
||||||
&self,
|
&self,
|
||||||
main_channel: usize,
|
main_channel: usize,
|
||||||
@@ -281,23 +471,13 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
.slice_cztyx(s![main_channel, .., 0, .., ..])?
|
.slice_cztyx(s![main_channel, .., 0, .., ..])?
|
||||||
.as_array::<f64>()?;
|
.as_array::<f64>()?;
|
||||||
let mut transforms = HashMap::new();
|
let mut transforms = HashMap::new();
|
||||||
let shape = vec![self.shape().z, self.shape().y, self.shape().x];
|
|
||||||
let center = shape
|
|
||||||
.iter()
|
|
||||||
.map(|&i| (i as f64 - 1.0) / 2.0)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
||||||
let name = metadata
|
let name = metadata
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| format!("channel{}", channel));
|
.unwrap_or_else(|| format!("channel{}", channel));
|
||||||
if channel == main_channel {
|
if channel == main_channel {
|
||||||
transforms.insert(
|
transforms.insert(name, Transform::default());
|
||||||
name,
|
|
||||||
Transform::default()
|
|
||||||
.with_shape(shape.clone())
|
|
||||||
.with_center(center.clone()),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let max = self
|
let max = self
|
||||||
.slice_cztyx(s![channel, .., 0, .., ..])?
|
.slice_cztyx(s![channel, .., 0, .., ..])?
|
||||||
@@ -311,18 +491,295 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
Ok(transforms)
|
Ok(transforms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// calculate drift transforms in yx using this image view
|
||||||
pub fn calculate_drift_transforms_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
pub fn calculate_drift_transforms_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||||
|
let mut view = self.clone().into_dyn();
|
||||||
|
let axes = view.axes();
|
||||||
|
if axes.contains(&Axis::Z) {
|
||||||
|
view = view.max_proj(Axis::Z)?;
|
||||||
|
}
|
||||||
|
if axes.contains(&Axis::C) {
|
||||||
|
view = view.max_proj(Axis::C)?;
|
||||||
|
}
|
||||||
|
view = view.permute_axes(&[Axis::T, Axis::Y, Axis::X])?;
|
||||||
|
let n = view.shape().t;
|
||||||
|
let m = (n as f64).sqrt().round() as usize;
|
||||||
|
let t_groups = (0..=m)
|
||||||
|
.map(|i| (((i * n) as f64) / (m as f64)).round() as usize)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let t_keys = t_groups
|
||||||
|
.array_windows()
|
||||||
|
.map(|[start, end]| (start + end) / 2)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let t_pairs = t_keys
|
||||||
|
.iter()
|
||||||
|
.zip(t_groups.array_windows())
|
||||||
|
.flat_map(|(t_key, [start, end])| (*start..*end).map(|i| (*t_key, i)))
|
||||||
|
.chain(t_keys.array_windows().map(|[start, end]| (*start, *end)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let fmaxz_keys = t_keys
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
Ok((
|
||||||
|
t,
|
||||||
|
filter::gaussian_smooth(
|
||||||
|
&view.slice(s![*t, .., ..])?.as_array::<f64>()?,
|
||||||
|
&[5.0; 2],
|
||||||
|
)?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Result<HashMap<_, _>, Error>>()?;
|
||||||
|
let shifts = t_pairs
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(t_key, t)| {
|
||||||
|
if t_key == t {
|
||||||
|
Ok(Transform::<Ix2>::from_translation(&vec![0.0; view.ndim()]))
|
||||||
|
} else {
|
||||||
|
if let Some(fmaxz) = fmaxz_keys.get(&t_key) {
|
||||||
|
Ok(Transform::<Ix2>::register_translation(
|
||||||
|
fmaxz_keys[&t_key].view(),
|
||||||
|
fmaxz,
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
let fmaxz = filter::gaussian_smooth(
|
||||||
|
&view.slice(s![t, .., ..])?.as_array::<f64>()?,
|
||||||
|
&[5.0; 2],
|
||||||
|
)?;
|
||||||
|
Ok(Transform::<Ix2>::register_translation(
|
||||||
|
fmaxz_keys[&t_key].view(),
|
||||||
|
fmaxz.view(),
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let mut t = Transform::<Ix2>::from_translation(&[0.0; 2]);
|
||||||
|
let mut key_shifts = vec![t.clone()];
|
||||||
|
key_shifts.extend(shifts.iter().skip(n).map(|i| {
|
||||||
|
t = i * &t;
|
||||||
|
t.clone()
|
||||||
|
}));
|
||||||
|
Ok(key_shifts
|
||||||
|
.iter()
|
||||||
|
.zip(t_groups.array_windows())
|
||||||
|
.flat_map(|(s, [start, end])| {
|
||||||
|
let mut t = s.clone();
|
||||||
|
(*start..*end)
|
||||||
|
.map(|i| {
|
||||||
|
t = &shifts[i] * &t;
|
||||||
|
t.clone()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// calculate drift transforms in zyx using this image view
|
||||||
|
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
/// retrieve a frame and warp it according to the transform
|
||||||
todo!()
|
pub(crate) fn get_transformed_frame(
|
||||||
|
&self,
|
||||||
|
c: usize,
|
||||||
|
z: usize,
|
||||||
|
t: usize,
|
||||||
|
transform: &Transform<IxDyn>,
|
||||||
|
order: usize,
|
||||||
|
default_value: &DefaultValue,
|
||||||
|
) -> Result<Arc<Frame>, Error> {
|
||||||
|
match transform.ndim() {
|
||||||
|
2 => {
|
||||||
|
let tr = transform.clone().into_dimensionality::<Ix2>()?;
|
||||||
|
|
||||||
|
macro_rules! interpolate {
|
||||||
|
($array:expr ,$var:ident, bool) => {
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
tr.interpolate_par_order(
|
||||||
|
&$array.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||||
|
order,
|
||||||
|
default_value,
|
||||||
|
)?
|
||||||
|
.mapv(|i| i > 0.5),
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
($array:expr ,$var:ident, f64) => {
|
||||||
|
Ok(Arc::from(Frame::$var(tr.interpolate_par_order(
|
||||||
|
$array,
|
||||||
|
order,
|
||||||
|
default_value,
|
||||||
|
)?)))
|
||||||
|
};
|
||||||
|
($array:expr ,$var:ident, $t:ty) => {
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
tr.interpolate_par_order($array, order, default_value)?
|
||||||
|
.mapv(|i| i as $t),
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.get_cached_frame(c, z, t, None)?.as_ref() {
|
||||||
|
Frame::Bool(frame) => interpolate!(frame, Bool, bool),
|
||||||
|
Frame::I8(frame) => interpolate!(frame, I8, i8),
|
||||||
|
Frame::U8(frame) => interpolate!(frame, U8, u8),
|
||||||
|
Frame::I16(frame) => interpolate!(frame, I16, i16),
|
||||||
|
Frame::U16(frame) => interpolate!(frame, U16, u16),
|
||||||
|
Frame::I32(frame) => interpolate!(frame, I32, i32),
|
||||||
|
Frame::U32(frame) => interpolate!(frame, U32, u32),
|
||||||
|
Frame::U64(frame) => interpolate!(frame, U64, u64),
|
||||||
|
Frame::I64(frame) => interpolate!(frame, I64, i64),
|
||||||
|
Frame::I128(frame) => interpolate!(frame, I128, i128),
|
||||||
|
Frame::U128(frame) => interpolate!(frame, U128, u128),
|
||||||
|
Frame::F32(frame) => interpolate!(frame, F32, f32),
|
||||||
|
Frame::F64(frame) => interpolate!(frame, F64, f64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let tr = transform.clone().into_dimensionality::<Ix3>()?;
|
||||||
|
let shape = self.shape();
|
||||||
|
let center = shape.iter().map(|&i| (i as f64) / 2.0).collect::<Vec<_>>();
|
||||||
|
let zf = z as f64;
|
||||||
|
let yf = shape.y as f64 - 1.0;
|
||||||
|
let xf = shape.x as f64 - 1.0;
|
||||||
|
let corners = array![[zf, 0.0, 0.0], [zf, 0.0, xf], [zf, yf, 0.0], [zf, yf, xf]];
|
||||||
|
let extent = tr.transform_points(&corners, ¢er)?;
|
||||||
|
let z_extent = extent.slice(s![.., 2]);
|
||||||
|
let z_min = (z_extent
|
||||||
|
.iter()
|
||||||
|
.min_by(|a, b| a.total_cmp(b))
|
||||||
|
.unwrap()
|
||||||
|
.floor() as isize
|
||||||
|
- 2)
|
||||||
|
.max(0) as usize;
|
||||||
|
let z_max = (z_extent
|
||||||
|
.iter()
|
||||||
|
.max_by(|a, b| a.total_cmp(b))
|
||||||
|
.unwrap()
|
||||||
|
.ceil() as isize
|
||||||
|
+ 2)
|
||||||
|
.min(shape.z as isize - 1)
|
||||||
|
.max(0) as usize;
|
||||||
|
let frames = (z_min..=z_max)
|
||||||
|
.map(|zi| self.get_cached_frame(c, zi, t, None))
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
|
||||||
|
// TODO; check these translations
|
||||||
|
let u0 = tr.as_translation(&[z_min as f64, 0.0, 0.0]);
|
||||||
|
let u1 = tr.as_translation(&[-(z_min as f64), 0.0, 0.0]);
|
||||||
|
let tr = u0 * tr * u1;
|
||||||
|
|
||||||
|
match frames.first().map(|f| f.as_ref()) {
|
||||||
|
Some(first) => {
|
||||||
|
macro_rules! interpolate {
|
||||||
|
($var:ident, bool) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed = tr.interpolate_par_order(
|
||||||
|
&stack.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||||
|
order,
|
||||||
|
default_value,
|
||||||
|
)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).mapv(|i| i > 0.5),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
($var:ident, f64) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed =
|
||||||
|
tr.interpolate_par_order(&stack, order, default_value)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).to_owned(),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
($var:ident, $t:ty) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed =
|
||||||
|
tr.interpolate_par_order(&stack, order, default_value)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).mapv(|i| i as $t),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
match first {
|
||||||
|
Frame::Bool(_) => interpolate!(Bool, bool),
|
||||||
|
Frame::I8(_) => interpolate!(I8, i8),
|
||||||
|
Frame::U8(_) => interpolate!(U8, u8),
|
||||||
|
Frame::I16(_) => interpolate!(I16, i16),
|
||||||
|
Frame::U16(_) => interpolate!(U16, u16),
|
||||||
|
Frame::I32(_) => interpolate!(I32, i32),
|
||||||
|
Frame::U32(_) => interpolate!(U32, u32),
|
||||||
|
Frame::F32(_) => interpolate!(F32, f32),
|
||||||
|
Frame::F64(_) => interpolate!(F64, f64),
|
||||||
|
Frame::I64(_) => interpolate!(I64, i64),
|
||||||
|
Frame::U64(_) => interpolate!(U64, u64),
|
||||||
|
Frame::I128(_) => interpolate!(I128, i128),
|
||||||
|
Frame::U128(_) => interpolate!(U128, u128),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let shape = self.shape();
|
||||||
|
Ok(Arc::new(match self.pixel_type() {
|
||||||
|
PixelType::Bool => Frame::Bool(Array2::default((shape.y, shape.x))),
|
||||||
|
PixelType::I8 => Frame::I8(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U8 => Frame::U8(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I16 => Frame::I16(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U16 => Frame::U16(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I32 => Frame::I32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U32 => Frame::U32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::F32 => Frame::F32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::F64 => Frame::F64(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I64 => Frame::I64(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U64 => Frame::U64(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I128 => Frame::I128(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U128 => Frame::U128(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::F128 => Err(Error::F128NotSupported)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d => Err(Error::TransformationNotImplemented(d)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::axes::Axis;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::readers::{DynReader, Reader};
|
||||||
use crate::transforms::Transforms;
|
use crate::transforms::Transforms;
|
||||||
|
use image_registration::transform::Transform;
|
||||||
|
use ndarray::s;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn open(file: &str) -> Result<DynReader, Error> {
|
||||||
|
let path = std::env::current_dir()?
|
||||||
|
.join("tests")
|
||||||
|
.join("files")
|
||||||
|
.join(file);
|
||||||
|
DynReader::new(&path, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -348,4 +805,73 @@ mod tests {
|
|||||||
assert_eq!(t.channel["TV2"].ndim(), 2);
|
assert_eq!(t.channel["TV2"].ndim(), 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transforms_as_array() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let file = "czi/beads_2020_08_05__11_43_07.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view();
|
||||||
|
let mut channel = HashMap::new();
|
||||||
|
channel.insert(
|
||||||
|
"TV1".to_string(),
|
||||||
|
Transform::new(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]),
|
||||||
|
);
|
||||||
|
channel.insert(
|
||||||
|
"TV2".to_string(),
|
||||||
|
Transform::new(vec![
|
||||||
|
0.9995868322142533,
|
||||||
|
0.012140955373934502,
|
||||||
|
0.012592501668218145,
|
||||||
|
0.8571690967587099,
|
||||||
|
-5.270859902118885,
|
||||||
|
-5.81118547158953,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
let view_t = view.with_transforms(Transforms {
|
||||||
|
default_value: Default::default(),
|
||||||
|
interpolation_order: None,
|
||||||
|
channel,
|
||||||
|
drift: Vec::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let s_a = view.max_proj(Axis::Z)?.slice_cztyx(s![1, 0, .., .., ..])?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"s_a: {:?}, {:?}, {:?}",
|
||||||
|
s_a.get_slice(),
|
||||||
|
s_a.get_axes(),
|
||||||
|
s_a.get_operations()
|
||||||
|
);
|
||||||
|
|
||||||
|
let s_b = view_t
|
||||||
|
.max_proj(Axis::Z)?
|
||||||
|
.slice_cztyx(s![1, 0, .., .., ..])?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"s_b: {:?}, {:?}, {:?}",
|
||||||
|
s_b.get_slice(),
|
||||||
|
s_b.get_axes(),
|
||||||
|
s_b.get_operations()
|
||||||
|
);
|
||||||
|
|
||||||
|
let a = s_a.as_array::<u16>()?;
|
||||||
|
let b = s_b.as_array::<u16>()?;
|
||||||
|
assert_ne!(a, b);
|
||||||
|
|
||||||
|
// let a = view.slice_cztyx(s![1, 0, 0, .., ..])?.as_array::<u16>()?;
|
||||||
|
// let b = view_t.slice_cztyx(s![1, 0, 0, .., ..])?.as_array::<u16>()?;
|
||||||
|
// assert_ne!(a, b);
|
||||||
|
//
|
||||||
|
// let a = view
|
||||||
|
// .max_proj(Axis::Z)?
|
||||||
|
// .slice(s![1, 0, .., ..])?
|
||||||
|
// .as_array::<u16>()?;
|
||||||
|
// let b = view_t
|
||||||
|
// .max_proj(Axis::Z)?
|
||||||
|
// .slice(s![1, 0, .., ..])?
|
||||||
|
// .as_array::<u16>()?;
|
||||||
|
// assert_ne!(a, b);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+604
-147
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+3234
File diff suppressed because it is too large.
Load diff
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Channels": [{
|
||||||
|
"Name": "mScarlet_GFP-mSc-filter",
|
||||||
|
"DisplayMode": 1,
|
||||||
|
"HistogramMax": -1,
|
||||||
|
"Max": 523,
|
||||||
|
"Gamma": 1,
|
||||||
|
"Min": 352,
|
||||||
|
"Color": -1
|
||||||
|
}],
|
||||||
|
"Comments": {"Summary": "5-GFP-mScarlet/GFP/OD2.0/100x/1x/2x2binning/15s_interval/{-2,2,0/5zstack}/autofocus/mScarlet_GFP-mSc-filter/500ms/100% OR: GFP 20%/exp150ms"}
|
||||||
|
}
|
||||||
@@ -26,8 +26,10 @@ def image(array):
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"s",
|
"s",
|
||||||
|
list(
|
||||||
combinations_with_replacement(
|
combinations_with_replacement(
|
||||||
(0, -1, 1, slice(None), slice(0, 1), slice(-1, 0), slice(1, 1)), 5
|
(0, -1, 1, slice(None), slice(0, 1), slice(-1, 0), slice(1, 1), slice(1, 2)), 5
|
||||||
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_slicing(s, image, array):
|
def test_slicing(s, image, array):
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ def image(array):
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"fun_and_axis",
|
"fun_and_axis",
|
||||||
|
list(
|
||||||
product(
|
product(
|
||||||
(
|
(
|
||||||
np.sum,
|
np.sum,
|
||||||
@@ -44,6 +45,7 @@ def image(array):
|
|||||||
np.nanstd,
|
np.nanstd,
|
||||||
),
|
),
|
||||||
(None, 0, 1, 2, 3, 4),
|
(None, 0, 1, 2, 3, 4),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_ufuncs(fun_and_axis, image, array):
|
def test_ufuncs(fun_and_axis, image, array):
|
||||||
|
|||||||
Reference in new issue
Block a user