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
11 changes: 0 additions & 11 deletions .claude/freshen-package-status

This file was deleted.

19 changes: 16 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
name = "RegisterHindsight"
uuid = "73fda715-febb-5522-a0fc-086e91958f61"
version = "1.0.0"
authors = ["Tim Holy <tim.holy@gmail.com>"]
version = "0.3.0"

[deps]
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
RegisterDeformation = "c19381b7-cf49-59d7-881c-50dfbd227eaf"
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
RegisterPenalty = "464fa2a9-b19c-5c59-8698-f58c971f971e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
Aqua = "0.8"
Documenter = "1"
ExplicitImports = "1"
ForwardDiff = "0.10, 1"
ImageCore = "0.8.1, 0.9, 0.10"
ImageMagick = "1"
Interpolations = "0.15, 0.16"
OffsetArrays = "0.10, 0.11, 1"
ProgressMeter = "0.7, 0.8, 0.9, 1"
Random = "1"
RegisterCore = "1"
RegisterDeformation = "1"
RegisterMismatch = "1"
RegisterPenalty = "1"
StaticArrays = "0.10, 0.11, 0.12, 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"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RegisterCore = "67712758-55e7-5c3c-8e85-dda1d7758434"
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"

[targets]
test = ["Test", "ForwardDiff", "ImageMagick", "RegisterCore", "TestImages"]
test = ["Aqua", "Documenter", "ExplicitImports", "Test", "ForwardDiff", "ImageMagick", "Random", "RegisterCore", "RegisterMismatch", "TestImages"]
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# RegisterHindsight

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

RegisterHindsight refines image-registration deformation fields using gradient
descent. Given a `fixed` image and a `moving` image, it adjusts the
displacements of a `GridDeformation` to minimize the mean-square intensity
error subject to a deformation-smoothness penalty.

It is part of the
[HolyLab registration pipeline](https://github.com/HolyLab/HolyLabRegistry.git)
and is designed to be called after a coarse registration step (e.g.,
[RegisterOptimize](https://github.com/HolyLab/RegisterOptimize.jl)) to squeeze
out residual misalignment.

## Installation

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

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

## Concept

"Hindsight" refers to the optimization strategy: rather than working with the
raw displacement values on the deformation grid, the optimizer adjusts the
*interpolation coefficients* that back the deformation field. This exposes a
smooth, differentiable objective that can be descended with a simple line
search.

The deformation `ϕ` must be an **interpolating deformation** — a
`GridDeformation` whose displacement field is backed by a
`ScaledInterpolation` with `InPlace` boundary conditions. Construct one with:

```julia
ϕ = interpolate!(copy(ϕ0)) # note: interpolate! (with !), not interpolate
```

`interpolate(ϕ0)` (without `!`) produces a different, incompatible type.

## Usage

```julia
using RegisterDeformation, RegisterPenalty, Interpolations
using RegisterHindsight

# 1-D example: shift a sine wave by ~0.05 radians
fixed = sin.(range(0, stop=4π, length=40))
moving = sin.(range(0.2, stop=4π + 0.2, length=40))

nodes = (range(1, stop=40, length=5),)
ϕ = interpolate!(GridDeformation(zeros(1, 5), nodes))

ap = AffinePenalty(ϕ.nodes, 0.01) # smoothness penalty weight 0.01

result = RegisterHindsight.optimize!(ϕ, ap, fixed, moving; stepsize=0.1)

result.final < result.initial # true — penalty decreased
```

`optimize!` returns a named tuple `(; final, initial)` with the penalty
before and after optimization, so you can check how much improvement was
achieved.

### Penalty functions

The penalty and its gradient can also be called directly, which is useful for
diagnostics or for building a custom optimizer:

| Function | Description |
|---|---|
| `RegisterHindsight.penalty_hindsight(ϕ, dp, fixed, moving)` | Total penalty (data + regularization) |
| `RegisterHindsight.penalty_hindsight!(g, ϕ, dp, fixed, moving)` | Total penalty + gradient in `g` |
| `RegisterHindsight.penalty_hindsight_data(ϕ, fixed, moving)` | Data (intensity error) term only |
| `RegisterHindsight.penalty_hindsight_reg(ϕ, dp)` | Regularization term only |

A two-deformation overload `penalty_hindsight(ϕ1, ϕ2, dp, fixed, moving)`
evaluates both deformations on the same set of valid voxels so the two
penalties are directly comparable.

> **Note:** `optimize!` is intentionally not exported because it would
> conflict with `RegisterOptimize.optimize!`. Call it as
> `RegisterHindsight.optimize!(...)`.
Loading
Loading