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
63 changes: 0 additions & 63 deletions CHANGELOG.md

This file was deleted.

21 changes: 18 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "RegisterOptimize"
uuid = "b981a1b5-4587-53b0-9c3d-454c648f6f8d"
authors = ["Tim Holy <tim.holy@gmail.com>"]
version = "1.0.0"
authors = ["Tim Holy <tim.holy@gmail.com>"]

[deps]
CachedInterpolations = "b9709bfb-d23d-5560-8276-8c35c4b76823"
Expand All @@ -21,39 +21,54 @@ ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
RegisterCore = "67712758-55e7-5c3c-8e85-dda1d7758434"
RegisterDeformation = "c19381b7-cf49-59d7-881c-50dfbd227eaf"
RegisterFit = "36121b08-3789-5198-aff2-59a3443d9b59"
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
RegisterPenalty = "464fa2a9-b19c-5c59-8698-f58c971f971e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Aqua = "0.8"
Documenter = "1"
CachedInterpolations = "1"
CenterIndexedArrays = "1"
CoordinateTransformations = "0.5, 0.6"
ExplicitImports = "1"
ForwardDiff = "0.10, 1"
ImageMagick = "1"
Images = "0.26"
Interpolations = "0.9, 0.10, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16"
Ipopt = "0.5, 0.6, 1"
IterativeSolvers = "0.7, 0.8, 0.9"
JuMP = "1.2"
LinearAlgebra = "1"
MathOptInterface = "1"
NLsolve = "3, 4"
Optim = "0.18, 0.19, 0.20, 1"
Printf = "1"
ProgressMeter = "0.7, 0.8, 0.9, 1"
RegisterCore = "1"
RegisterDeformation = "1"
RegisterFit = "1"
RegisterMismatch = "1"
RegisterPenalty = "1"
RegisterUtilities = "1"
Rotations = "1"
StaticArrays = "0.10, 0.11, 0.12, 1"
Statistics = "1"
Test = "1"
TestImages = "1"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
RegisterUtilities = "d4862ba2-f42c-5aeb-af4f-96a8884a16c4"
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"

[targets]
test = ["Test", "Rotations", "RegisterUtilities", "ImageMagick", "Images", "TestImages"]
test = ["Aqua", "Documenter", "ExplicitImports", "Test", "Rotations", "RegisterMismatch", "RegisterUtilities", "ImageMagick", "Images", "TestImages"]
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# RegisterOptimize.jl

[![CI](https://github.com/HolyLab/RegisterOptimize.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/HolyLab/RegisterOptimize.jl/actions/workflows/CI.yml)
[![codecov](https://codecov.io/gh/HolyLab/RegisterOptimize.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/HolyLab/RegisterOptimize.jl)

RegisterOptimize provides optimization routines for image registration: given a pair of images, it finds the rigid or deformable transformation that minimizes the mismatch between them. It is part of the [HolyLab image registration ecosystem](#ecosystem).

## Installation

This package is registered in the [HolyLabRegistry](https://github.com/HolyLab/HolyLabRegistry). Add that registry once, then install normally:

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

## Ecosystem

RegisterOptimize sits at the optimization end of a multi-package pipeline:

| Package | Role |
|---------|------|
| [RegisterMismatch.jl](https://github.com/HolyLab/RegisterMismatch.jl) | Compute block-wise mismatch between image pairs |
| [RegisterFit.jl](https://github.com/HolyLab/RegisterFit.jl) | Fit quadratic models to mismatch data |
| [RegisterPenalty.jl](https://github.com/HolyLab/RegisterPenalty.jl) | Regularization penalties for deformations |
| [RegisterDeformation.jl](https://github.com/HolyLab/RegisterDeformation.jl) | Deformation types and coordinate transformations |
| **RegisterOptimize.jl** | **Optimize the registration** |

## Concepts

### Mismatch data

The main optimization routines work on *mismatch data* rather than raw images. For each block of the image grid, mismatch data records how much the two images differ as a function of local shift. This representation is precomputed by RegisterMismatch and interpolated before being passed to RegisterOptimize, enabling fast repeated evaluations during optimization.

### Regularization

Deformable registration is ill-posed: without constraints, the optimizer can find deformations that align images locally but are physically unrealistic. A regularization penalty (controlled by strength `λ`) penalizes deformations that deviate from an affine map. Larger `λ` gives smoother but less locally accurate registration; smaller `λ` gives more flexibility but risks overfitting.

`fixed_λ` optimizes at a single user-supplied `λ`. `auto_λ` searches over a range and selects the best value automatically by fitting a sigmoid to the data-penalty curve.

### Rigid vs. deformable

- **Rigid** (`RegisterOptimize.optimize_rigid`): finds the rotation + translation that best aligns two raw images. Useful as an initial pass when images differ primarily by global motion.
- **Deformable** (`fixed_λ`, `auto_λ`): finds a smooth spatially-varying warp field that aligns images locally.

## Usage

### Deformable registration at a fixed λ

```julia
using RegisterMismatch, RegisterFit, RegisterPenalty, RegisterOptimize

# 1. Compute block-wise mismatch between images
mms = mismatch_apertures(fixed, moving, aperture_centers, aperture_width, maxshift;
normalization=:pixels)

# 2. Fit quadratic models; get interpolated mismatch arrays
thresh = 0.5^ndims(fixed) * length(fixed) / prod(gridsize)
cs, Qs, mmis = mms2fit(mms, thresh)

# 3. Set up a deformation grid and regularization penalty
nodes = map(d -> range(1, stop=size(fixed, d), length=gridsize[d]), 1:ndims(fixed))
ap = AffinePenalty(nodes, λ) # λ controls regularization strength

# 4. Optimize: returns the deformation and its total penalty
ϕ, penalty = fixed_λ(cs, Qs, nodes, ap, mmis)
```

`ϕ` is a `GridDeformation` from RegisterDeformation.jl. Apply it with `transform(moving, ϕ)`.

### Automatic λ selection

When you don't know a suitable `λ` in advance, let `auto_λ` find it:

```julia
ϕ, penalty, λ_best, λ_all, datapenalty, quality =
auto_λ(fixed, moving, gridsize, maxshift, (λmin, λmax))
```

`auto_λ` tests a logarithmic sweep of `λ` values from `λmin` to `λmax`, fits a sigmoid to the data-penalty curve, and returns the deformation at the automatically chosen `λ_best`. Plot `datapenalty` vs `λ_all` (log x-axis) to verify the sigmoidal shape; if it is not sigmoidal, widen the range. A good first guess is `(λmin, λmax) = (1e-6, 100)`.

If mismatch data are already computed, pass them directly to avoid recomputing:

```julia
ϕ, penalty, λ_best, λ_all, datapenalty, quality =
auto_λ(cs, Qs, nodes, mmis, (λmin, λmax))
```

### Rigid registration

```julia
using RegisterOptimize, CoordinateTransformations, LinearAlgebra

tform0 = AffineMap(Matrix(1.0I, 2, 2), zeros(2)) # identity initial guess
tform, fval = RegisterOptimize.optimize_rigid(fixed, moving, tform0, maxshift)
```

The returned `tform` is an `AffineMap` (rotation + translation) and `fval` is the normalized sum-of-squared-differences at the solution.

### Temporal image sequences

For a time series of images, add a temporal roughness penalty `λt` to penalize frame-to-frame variation in the deformation:

```julia
# Fixed spatial λ and temporal λt
ϕs, penalty = fixed_λ(cs, Qs, nodes, ap, mmis; λt=0.1)

# Or find a good λt automatically from the quadratic approximation
λts, datapenalty = auto_λt(Es, cs, Qs, ap, (1e-6, 1.0))
```

`ϕs` is a `Vector{GridDeformation}`, one per frame. Plot `datapenalty` vs `λts` (log x-axis) to find the "kink" where `λt` begins to constrain the optimization.
Loading
Loading