Metadata-Version: 2.5
Name: Relative-Intensity-Pattern-Registration
Version: 0.4.0
Summary: Gain-invariant, robust registration of microscopy time series
Project-URL: Homepage, https://github.com/Jay2owe/Relative-Intensity-Pattern-Registration
Project-URL: Source, https://github.com/Jay2owe/Relative-Intensity-Pattern-Registration
Project-URL: Issues, https://github.com/Jay2owe/Relative-Intensity-Pattern-Registration/issues
Author-email: Jamie Malcolm <jamiemalcolm12@gmail.com>
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: Fiji,ImageJ,drift-correction,image-alignment,live-imaging,microscopy,registration,time-lapse
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Image Processing
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: opencv-python-headless<5,>=4.8
Requires-Dist: scikit-image>=0.21
Requires-Dist: scipy>=1.10
Requires-Dist: tifffile>=2023.7.10
Provides-Extra: test
Requires-Dist: build>=1.2; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# Relative-Intensity Pattern Registration (RIPR)

## Accepted longitudinal recipes

```python
import ripr
result = ripr.register_accepted("recording.tif", recipe="bright_dim", channel=1)
# recipe="landmarks" or "moving_cells" runs those explicit accepted routes.
```

For an array, use `ripr.register_accepted(stack, axes="TCYX", channel=2)`.
Inspect `result.provenance` and `result.registration.warnings`. The selected channel
alone drives estimation; every channel and Z plane receives the resulting transforms.
There is no automatic longitudinal router or Python substitute.
Output sampling defaults to nearest neighbour to preserve pixel counts. For the
bilinear rendering used in benchmark review TIFFs, pass `interpolation="bilinear"`;
this changes rendered pixels, not the fitted transforms.

The accepted native routes require **64-bit Windows and Java 25 or newer**; set
`RIPR_LONGITUDINAL_JAVA` to its `java.exe` if it is not discoverable. The wheel/sdist
includes the Java plugin, ImageJ and the frozen native scientific runtime. A missing
runtime fails visibly. Existing `register` calls and legacy modes remain unchanged.
Microsoft's x64 Visual C++ 2015–2022 Redistributable must be installed on Windows;
its proprietary runtime DLLs are not bundled. Obtain it from Microsoft's official
[download page](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist), not an unofficial DLL download service.

Bright/dim includes accepted phase-seed recovery and the hardware-capped speed
policy; Moving cells includes accepted guarded Bright/dim fallback. Original accepted
scientific bytecode is retained, rather than silently rebuilt from later experiments.
Warnings identify weak evidence and actual recovery. Pair residuals/gain unavailable
from the native protocol remain NaN (`support=-1` means unavailable); none is an
accuracy score. This release does not guarantee unattended correctness or establish
real-recording geometric ground truth. Review before analysis.

Automation may use the existing `ripr.actions.dispatch("register", ...)` with
`parameters.selection_mode="accepted_longitudinal"` (Bright/dim or Landmarks, chosen
by the declared image type) or `"accepted_moving_cells"`. Use an explicit
image-and-motion preset with `motion_type="intermittent_jumps"`; the fitting recipe
and execution policy are frozen. Existing overwrite-confirmation rules still apply.

This Python package runs the same registration operation as the Java Fiji/ImageJ plugin. Java is the
default engine because it is much faster; a Python/NumPy/SciPy engine remains available as a fallback.
It does not launch ImageJ. On PyPI it is `Relative-Intensity-Pattern-Registration`; the import package
and terminal command are both `ripr`.

## Install

```powershell
python -m pip install Relative-Intensity-Pattern-Registration
```

The install name is the project's full name; everything you type afterwards is `ripr`:

```python
import ripr
```

On Windows, install into a virtual environment whose path is short. OpenCV ships a DLL whose
full path can exceed the 260-character limit from a deeply nested folder, and it fails at
import with `DLL load failed while importing cv2: The filename or extension is too long`,
which names cv2 rather than the real cause. A shorter path, or long paths enabled in Windows,
fixes it.

### From a checkout

From this folder:

```powershell
python -m pip install -e .
```

For development and tests:

```powershell
python -m pip install -e ".[test]"
pytest
```

## Quick start

For a TIFF, only the input filename is required. The output is written beside it as
`recording_registered.tif`:

```python
import ripr

ripr.register("recording.tif")
```

Choose a destination with `output_path="registered/recording.tif"`; for path input,
`ripr.register("recording.tif", "registered/recording.tif")` is also accepted.

For a NumPy array, only the array is required when it is shaped `T, Y, X`:

```python
result = ripr.register(stack)
corrected = result.corrected
```

The normal interface has three choices:

```python
result = ripr.register(
    stack,
    recipe="landmarks",  # or "bright_dim" / "moving_cells"
    channel=1,           # one-based
    longitudinal=True,
)
```

They default to the benchmark-backed Landmarks category for phase contrast, channel 1, and
whole-recording longitudinal processing. Bright/dim selects the accepted fluorescence or
bioluminescence category. Moving cells is the separate biological-foreground route and is used with
`longitudinal=False`. With `longitudinal=False`, the selected category is resolved to a concrete
automatic recipe; inspect `result.automatic_selection.recipe` and `result.provenance` for its ID.
Java is preferred for execution.

## Expert recording settings

```python
from ripr import register

result = register(
    "recording.tif",
    output_path="recording_registered.tif",
    recipe="landmarks",
    channel=1,
    longitudinal=False,
    image_type="phase_contrast",
    motion_type="subpixel_random_walk",
    max_shift=20,
    max_iterations=50,
    interpolation="bilinear",
    backend="python",
)

print([(t.dx, t.dy, t.theta) for t in result.transforms])  # theta is radians
print(result.registration.log2_gain)       # bleaching/lamp-drift trace
print(result.median_residual_before, result.median_residual_after)
```

Every `LogRatioParameters` field can be passed directly. `image_type` and `motion_type` choose the
starting recipe; the remaining fields override one expert setting at a time. Use a parameters
object only when you want to reuse or inspect a complete bundle.

Use `ripr.rank_channels(array, axes="TCZYX")` to rank estimation channels by localisability before
a run. Values below `ripr.WARN_BELOW` carry the same poor-localisability warning threshold as the
ImageJ plugin.

The input is never modified. For hyperstacks, pass axes explicitly, for example `TCZYX`. `channel`,
`slice`, and `reference_frame` in `LogRatioParameters` are one-based like ImageJ; `slice=0` maximum-
projects Z for movement estimation. The estimated transform is applied unchanged to every channel and
Z plane.

## Run the estimation in Java, at Java speed

The registration in this package and the registration in the Fiji plugin are the same operation, and
on a preset recipe they produce the same transforms bit for bit. They do not take the same amount of
time. Java aligns frame pairs across a thread pool, which is the one place this problem parallelises
well, and the NumPy engine here runs them one after another.

Java is used automatically when a Java runtime and the plugin jar are both present:

```python
result = register(stack, parameters, axes="TYX")
```

Measured on one 40-frame 448x768 recording, 16 cores, identical settings and identical output:

| Engine | Time |
|---|---|
| `backend="java"` | 16.6 s |
| `backend="python"` | over 900 s |

The optional `backend` override takes:

- omitted — prefer Java; warn clearly before falling back to Python
- `"java"` — require Java and raise if it cannot run
- `"python"` — deliberately use NumPy and do not warn about Java
- `"auto"` — prefer Java; warn clearly before falling back to Python

Set `RIPR_BACKEND` to `java`, `auto`, or `python` to choose the policy for a whole process. An explicit
`java` setting is strict. A fallback warning includes the reason Java could not be used and how to fix
it; it is never silent.

Only transforms cross the process boundary; warping happens here either way, so the choice changes
how long a run takes and not what it gives back. Two consequences worth knowing:

- `result.registration.pairs` is empty under the Java backend. Per-pair fits are not carried across,
  because moving them costs more than a caller asking for a fast path wants to spend. Everything
  reported per frame is present and is the Java engine's own value.
- The Java runner rebuilds the recipe from the image type, motion type and selection mode you name.
  That is exact for a preset recipe and wrong for a customised one, so a recipe that differs from its
  preset in any other field stays on the Python engine. `ripr.registration.java_incompatibilities()`
  lists what is blocking it; `backend="java"` raises rather than silently running something else.
The backend finds its pieces from the environment: `RIPR_JAVA` or `JAVA_HOME` or `java` on `PATH`
for the runtime, and `RIPR_JAR` or a `jars/` directory beside the package or `RIPR_FIJI` for the
plugin. `ripr.java_backend.available()` reports whether it can run at all.

## Register a TIFF or folder

```python
from ripr import register, register_batch

register("recording.ome.tif")
register_batch("input_folder", "output_folder")
```

`register_file` remains available as a compatibility alias for older code.

Or from a shell:

```powershell
ripr recording.tif

ripr fluorescence.tif --recipe bright_dim --channel 2

ripr rotating_recording.tif --no-longitudinal --fit-rotation --max-rotation-degrees 10

ripr remounted_recording.tif remounted_registered.tif `
  --no-longitudinal --rotation-mode known_events --rotation-events 25,51 `
  --rotation-event-window 3 --max-rotation-degrees 10

ripr input_folder output_folder --recursive
```

Folder batches create `log_ratio_batch_report.csv`, skip existing outputs unless `--overwrite` is set,
and continue after a damaged or incompatible input. The existing report columns are followed by the
resolved rotation mode, one-based event list, window and compact event diagnostics.

## Memory on long recordings

File registration reads compatible TIFFs through a read-only memory map. Compressed TIFFs use the
normal decoder. Channel and slice selection reuse the input where possible, and integer pixels stay
in their original type until a numerical operation needs floating point. Batches release each
corrected video before starting the next one.
Automation scripts reuse large in-memory arrays through an `image` variable instead of embedding
their pixels in generated Python text. File-path requests remain self-contained.

The Python engine releases image pyramids after their last use, puts large preprocessed stacks in
temporary storage, and uses sparse movement equations
above 128 frames. Bright/dim refinement retains reference features and active frames; landmark
reference medians process spatial strips while still using every frame. No frames are dropped and
the registration resolution and precision are unchanged. The corresponding Java source also bounds
preprocessing, pyramid and temporal-scoring caches. Selected-pixel masks are made as needed.
Large landmark feature stacks use temporary files in both engines; aligned strips still include
every frame in the exact median. Stored features are reused, and temporary files are removed
when the run finishes or fails. Allow approximately eight bytes per selected-channel pixel across
all frames for peak landmark scratch disk space, plus the Java bridge's raw input copy.
Small feature stacks stay in memory. Fixed-reference Fourier transforms are reused across
longitudinal fits, and phase-correlation peak selection retains only the requested peaks.

The Java bridge reads its raw input on demand. With a compatible rebuilt Java engine, the Python
package also sizes the longitudinal Java heap from the recipe, frame dimensions, worker count, and per-frame
bookkeeping, so a long video does not cause Java to reserve a large fraction of system RAM just
for allocation headroom.
`RIPR_JAVA_HEAP_MB` can override that heap budget (an integer of at least 32). This budget excludes
Python arrays and Java's native overhead. Older Java engines retain their existing heap settings;
rebuild the Java plugin to use the new streaming behavior.

`register` still returns the complete corrected array, so output memory grows with video length.
Compressed input decoding can also require a complete input array. Use `estimate` when only movement
is required. For standalone reads,
`read_tiff(path, memory_map=True)` enables the same read-only mapping with decoder fallback.

## Java-to-Python interface map


| Java plugin/API | Python package |
|---|---|
| `RelativeIntensityPatternRegistration.register(ImagePlus, ...)` | `ripr.register(ndarray, ..., axes=...)` |
| `RelativeIntensityPatternRegistration.estimate(...)` | `ripr.estimate(...)` |
| `RelativeIntensityPatternParameters` | `ripr.LogRatioParameters` |
| `RelativeIntensityPatternRecommendations.forTypes(...)` | `ripr.recommendation(...)` |
| `StackWarper.apply(...)` | `ripr.apply_transforms(...)` |
| batch plugin | `ripr.register_batch(...)` |
| TIFF input/output | `ripr.register(path, output_path=...)` (`register_file` is a compatibility alias) |

Set `fit_rotation=True` and `max_rotation_degrees=<bound>` on `LogRatioParameters` to estimate bounded
in-plane rotation as well as translation. The public bound is in degrees; returned `Transform.theta`
values are radians. Both log-ratio and area-correlation estimators support the rigid search. Automatic is
a fixed declared image-and-motion rule and never inspects the recording to choose a recipe. Dense and
low-light fluorescence use `single_channel_emission_max_accuracy_r04_a208`: dense fluorescence
uses median-filtered previous-image Enhanced Correlation Coefficient, while sparse/low-light
fluorescence or bioluminescence uses the tuned log-ratio preset. Other image types retain
`recording_adaptive_selector_v1_user_approved_fixed_policy_v1`.

For long recordings with slow drift, gentle shake, isolated stage movements and major light changes,
choose the separate whole-recording route:

```python
from ripr import ImageType, LogRatioParameters, SelectionMode

parameters = LogRatioParameters(
    image_type=ImageType.SPARSE_LOW_LIGHT_FLUORESCENCE,
    selection_mode=SelectionMode.LONGITUDINAL_ACCURACY,
    channel=1,
)
```

This route uses bright/dim same-channel references for fluorescence or bioluminescence and edge/dark
landmarks for phase contrast or brightfield/DIC. It suppresses returning pulse-linked excursions while
retaining persistent and near-dark final jumps. It never reads another channel. Use Automatic instead
for repeated oscillation or continuous rotation.

For recordings that rotate only when they are removed and replaced, use the experimental event mode:

```python
from ripr import LogRatioParameters, RotationMode

parameters = LogRatioParameters.manual(
    rotation_mode=RotationMode.KNOWN_EVENTS,
    rotation_event_frames=(25, 51),  # one-based first frames after remounting
    rotation_event_window=3,
    max_rotation_degrees=10,
)
```

Each boundary uses all available before/after cross-pairs in the window and needs at least three usable
rigid fits. One robust angular jump is held exactly until the next event while translation remains free.
The final composed transforms are applied to the original pixels once. Event diagnostics are available
as `result.registration.event_rotations`; they include the event frame, incremental and cumulative angle,
candidate/usable/inlier counts, circular spread, contributing ranges and status. Large disagreement is a
warning; insufficient support stops the run. This mode uses the log-ratio estimator, does not support a
rolling reference, and remains opt-in pending validation on independent real remount recordings.

`Interpolation.NONE` remains the default: pure translations are rounded to whole pixels and applied
through a bit-exact block copy. A non-zero rotation cannot use that path, so `NONE` uses nearest-neighbour
sampling; bilinear and Catmull-Rom bicubic interpolation are opt-in for smoother intensity images.
`Interpolation.FOURIER` uses padded Fourier shifts for sharp, band-limited interpolation and represents
rotation as three Fourier shears. It can ring near hard edges.
Cropping defaults to the field containing real pixels in every registered frame.

This is a standalone Python package, separate from the Java plugin. It has no Swing dialogs or ImageJ
macro recorder; its settings are exposed through the Python API and command-line interface. The Java
plugin and Python package can continue to be used independently.

## Agent control

Automation clients can use the stable `ripr.actions` registry or its JSON-only
one-shot runner at `.claude/skills/ripr/scripts/ripr_runner.py` (the Codex
bridge is mirrored under `.codex/skills/ripr/`). The runner exposes TIFF
inspection, channel ranking, recommendations, estimation, single-image
registration, and folder batches. It returns diagnostics plus an equivalent
Python script; it never returns full pixel arrays. Existing output files need
explicit `confirm_overwrite=true` in runner requests. Concise public orientation
is available through `ripr.context.read()` and `ripr.context.search(...)`, which
return versioned structured envelopes for named topics and searches. The same guidance
is published for agents that cannot import Python as `README_AI.md` and
`ripr_context.json`; both are generated from the public context and included in the
source distribution and wheel.
