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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,36 @@ jobs:
- uses: codecov/codecov-action@v5
with:
file: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}
docs:
name: Documentation
runs-on: ubuntu-latest
permissions:
contents: write
statuses: write
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-docs-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-docs-${{ env.cache-name }}-
${{ runner.os }}-docs-
${{ runner.os }}-
- name: registry_add
run: julia -e 'using Pkg; pkg"registry add General https://github.com/HolyLab/HolyLabRegistry.git"'
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy docs
uses: julia-actions/julia-docdeploy@v1
with:
install-package: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
10 changes: 8 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "RegisterFit"
uuid = "36121b08-3789-5198-aff2-59a3443d9b59"
version = "1.0.0"
authors = ["Tim Holy <tim.holy@gmail.com>"]
version = "0.2.2"

[deps]
CenterIndexedArrays = "46a7138f-0d70-54e1-8ada-fb8296f91f24"
Expand All @@ -17,8 +17,11 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Aqua = "0.8"
CenterIndexedArrays = "0.0, 0.1, 0.2, 1"
CoordinateTransformations = "0.5, 0.6"
Documenter = "1"
ExplicitImports = "1"
ImageBase = "0.1"
ImageTransformations = "0.10"
Interpolations = "0.12, 0.13, 0.14, 0.15, 0.16"
Expand All @@ -34,9 +37,12 @@ Test = "1"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
ImageTransformations = "02fcd773-0e25-5acc-982a-7f6622650795"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["ImageBase", "ImageTransformations", "Test"]
test = ["Aqua", "Documenter", "ExplicitImports", "ImageBase", "ImageTransformations", "Test"]
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# RegisterFit.jl

[![CI](https://github.com/HolyLab/RegisterFit.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/HolyLab/RegisterFit.jl/actions/workflows/CI.yml)
[![Coverage](https://codecov.io/gh/HolyLab/RegisterFit.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/HolyLab/RegisterFit.jl)
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://HolyLab.github.io/RegisterFit.jl/stable)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://HolyLab.github.io/RegisterFit.jl/dev)

RegisterFit computes affine transformations that minimize image registration
mismatch. Given per-aperture mismatch data from
[RegisterMismatch](https://github.com/HolyLab/RegisterMismatch.jl), it finds
the best-fit affine transform by fitting each aperture's mismatch to a quadratic
form and solving a global least-squares problem. It is part of the
[HolyLab image registration pipeline](https://github.com/HolyLab).

## Installation

RegisterFit is distributed through the
[HolyLabRegistry](https://github.com/HolyLab/HolyLabRegistry). Add the registry
once, then install the package:

```julia
using Pkg
pkg"registry add https://github.com/HolyLab/HolyLabRegistry.git"
Pkg.add("RegisterFit")
```

## Quick start

### Fitting a single aperture's mismatch to a quadratic

```julia
using RegisterFit, RegisterCore

# Synthetic mismatch: parabola centred at shift (1, -2)
num = [(i - 1)^2 + (j + 2)^2 for i in -5:5, j in -5:5]
mm = MismatchArray(num, ones(11, 11))

E0, u0, Q = qfit(mm, 0.5)
# E0 β‰ˆ 0.0 β€” mismatch value at the minimum
# u0 β‰ˆ [1, -2] β€” shift at the minimum
# Q β‰ˆ I β€” curvature matrix
```

### Rigid alignment via the Principal Axes Transform

```julia
using RegisterFit

# Horizontal bar in fixed, vertical bar in moving (β‰ˆ 90Β° rotation)
fixed = zeros(5, 7); fixed[3, 2:6] .= 1.0
moving = zeros(7, 5); moving[2:6, 3] .= 1.0

tfms = pat_rotation(fixed, moving) # 2 candidate AffineMap transforms in 2D
# Evaluate each candidate against mismatch data and pick the best
```

## Overview

The typical workflow in the registration pipeline is:

1. **Compute mismatch** β€” per-aperture `MismatchArray`s from `RegisterMismatch`
2. **`mms2fit!`** β€” fit each aperture to a quadratic; returns per-aperture shifts
and curvature matrices
3. **`mismatch2affine`** β€” global least-squares solve over all apertures to
produce a single `AffineMap`
4. **Refine** β€” further optimisation in `RegisterDeformation`

See the [documentation](https://HolyLab.github.io/RegisterFit.jl/stable) for
the full API reference.
8 changes: 8 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
RegisterCore = "67712758-55e7-5c3c-8e85-dda1d7758434"
RegisterFit = "36121b08-3789-5198-aff2-59a3443d9b59"

[compat]
Documenter = "1"
julia = "1.10"
23 changes: 23 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Documenter
using RegisterFit
using RegisterCore

DocMeta.setdocmeta!(RegisterFit, :DocTestSetup, :(using RegisterFit, RegisterCore); recursive=true)

makedocs(;
modules=[RegisterFit],
sitename="RegisterFit.jl",
format=Documenter.HTML(;
canonical="https://HolyLab.github.io/RegisterFit.jl",
),
checkdocs=:exports,
pages=[
"Home" => "index.md",
"API Reference" => "api.md",
],
)

deploydocs(;
repo="github.com/HolyLab/RegisterFit.jl",
devbranch="master",
)
25 changes: 25 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# API Reference

## Global optimization

```@docs
mismatch2affine
pat_rotation
optimize_per_aperture
```

## Quadratic fitting

```@docs
qfit
mms2fit!
qbuild
```

## Displacement utilities

```@docs
uisvalid
uclamp!
principalaxes
```
105 changes: 105 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# RegisterFit.jl

```@docs
RegisterFit.RegisterFit
```

RegisterFit computes affine transformations that minimize image registration
mismatch. It is part of the [HolyLab](https://github.com/HolyLab) image
registration pipeline.

## Installation

RegisterFit is distributed through the
[HolyLabRegistry](https://github.com/HolyLab/HolyLabRegistry). Add the registry
once, then install:

```julia
using Pkg
pkg"registry add https://github.com/HolyLab/HolyLabRegistry.git"
Pkg.add("RegisterFit")
```

## Concepts

### Apertures and mismatch data

The registration pipeline divides an image into overlapping sub-regions called
**apertures**. For each aperture, `RegisterMismatch` computes a `MismatchArray`
that records the normalized squared difference between the fixed and moving images
as a function of shift. Each `MismatchArray` element stores a numerator/denominator
pair; only locations where `denom > thresh` are treated as valid.

### Quadratic fitting

Within a single aperture, the mismatch surface is often well approximated by a
quadratic:

```
mm(u) β‰ˆ E0 + (u βˆ’ u0)α΅€ Q (u βˆ’ u0)
```

[`qfit`](@ref) finds `E0` (minimum mismatch value), `u0` (shift at the minimum),
and `Q` (curvature matrix) for one aperture. [`mms2fit!`](@ref) does this for an
entire grid of apertures at once.

### Global affine solve

With one quadratic per aperture, [`mismatch2affine`](@ref) assembles a global
linear system whose solution is the affine transformation (rotation + shear +
translation) that best explains all aperture displacements simultaneously. The
result is an `AffineMap` from
[CoordinateTransformations.jl](https://github.com/JuliaGeometry/CoordinateTransformations.jl).

### Principal Axes Transform

[`pat_rotation`](@ref) provides a complementary rigid-alignment approach that
matches the intensity-weighted covariance ellipsoids of two images. It is useful
as an initial guess before running the quadratic optimization. Because an ellipsoid
is symmetric, there are 2 candidate rotations in 2D and 4 in 3D; you must evaluate
each against the mismatch data to pick the best one.

## Typical workflow

```julia
using RegisterFit, RegisterCore

# 1. Obtain per-aperture mismatch arrays from RegisterMismatch (not shown)
# mms :: Matrix{MismatchArray} (gridsize matches spatial dims)

# 2. Fit quadratics and prepare for interpolation
cs, Qs, mmis = mms2fit!(mms, thresh)

# 3. Solve for the global affine transform
tform = mismatch2affine(mms, thresh, knots)

# 4. (Optional) rigid pre-alignment with PAT
candidates = pat_rotation(fixed, moving)
# … pick best candidate, then refine with mismatch2affine / RegisterDeformation
```

## Quick examples

### Fitting one aperture

```julia
using RegisterFit, RegisterCore

num = [(i - 1)^2 + (j + 2)^2 for i in -5:5, j in -5:5]
mm = MismatchArray(num, ones(11, 11))

E0, u0, Q = qfit(mm, 0.5)
# E0 β‰ˆ 0.0, u0 β‰ˆ [1.0, -2.0], Q β‰ˆ I
```

### Rigid alignment via Principal Axes Transform

```julia
using RegisterFit

fixed = zeros(5, 7); fixed[3, 2:6] .= 1.0 # horizontal bar
moving = zeros(7, 5); moving[2:6, 3] .= 1.0 # vertical bar

tfms = pat_rotation(fixed, moving) # 2 candidate AffineMap transforms
# Select the candidate with the smallest mismatch
```
Loading
Loading