Moran's I
A simple plugin that computes Local Moran's I spatial-autocorrelation maps of 2-D images in napari.
A napari plugin that computes Local Moran's I spatial-autocorrelation maps of 2-D images, with a permutation-test–based significance map and a categorical cluster map (HH / LL / HL / LH / NS).
It is a Python port of the MATLAB reference implementation
moran_local.m
used in:
Dávid Cs. et al., "An image segmentation method based on the spatial correlation coefficient of Local Moran’s I - identification of A-type potassium channel clusters in the thalamus", eLife (2024). https://doi.org/10.7554/eLife.89361.1)
This code was entirely created using Claude Opus 4.7 (2026-05-06). Prompts have been archived in the prompts.txt file.
Demo

Features
- Layer chooser — operates on any 2-D
Imagelayer in the current viewer. - Configurable Moran order — controls the size of the Gaussian
neighbourhood kernel
(2·order + 1)². - Four standard significance levels — 0.05, 0.01, 0.001, 0.0001.
- Permutation test with user-defined number of repeats (default n = 200).
- Threaded execution via
napari.qt.threading.thread_worker, with a live progress bar and a Cancel button — the viewer stays responsive throughout. - Three output layers added back to the viewer for inspection:
Suffix Type Meaning _LocalIImage Local Moran's I per pixel (continuous) _SignificanceImage Binary mask: pixels with p ≤ sig_level _ClustersLabels Categorical cluster code (see legend below)
Cluster legend
| Code | Class | Meaning |
|---|---|---|
| 0 | NS | Not significant |
| 1 | HH | High-value pixel surrounded by high-value neighbours |
| 2 | LL | Low-value pixel surrounded by low-value neighbours |
| 3 | LH | Low-value pixel surrounded by high-value neighbours |
| 4 | HL | High-value pixel surrounded by low-value neighbours |
These codes match the original MATLAB source one-for-one.
Usage
- Launch napari and open or drag-drop an image.
- Open Plugins → Moran's I Analysis.
- (Optional) Try the bundled demo via File → Open Sample → napari-morans-i: Moran's I sample (blocks).
- Choose the input image layer, the Moran order, the significance level, and the number of permutations.
- Click Calculate. Progress is reported live; Cancel stops the permutation test cleanly.
- Inspect the three new layers added to the viewer.
This napari plugin was generated with copier using the napari-plugin-template (None).
Installation
You can install napari-morans-i via pip:
pip install napari-morans-i
If napari is not already installed, you can install napari-morans-i with napari and Qt via:
pip install "napari-morans-i[all]"
To install latest development version:
pip install git+https://github.com/lukasjarzembowski/napari-morans-i.git
Algorithm — step-by-step
The implementation lives in napari_morans_i/_core.py and reproduces the
MATLAB script step-for-step. Each step has a corresponding pure function so it
is independently testable.
1. Z-normalisation (z_normalize)
Convert the input image X into z-scores using the sample standard
deviation (ddof=1), to match MATLAB's default std:
z = (X − mean(X)) / std(X, ddof=1)
If the image is constant (std = 0) we short-circuit to an all-zeros array.
2. Gaussian neighbourhood weights (gaussian_weight_matrix)
Build a (2·order + 1) × (2·order + 1) Gaussian kernel W with
σ = (order + 1) / 1.7 and the centre cell zeroed, exactly as in the
MATLAB reference. Zeroing the centre means each pixel never contributes to
its own neighbourhood average.
3. Lagged values (local_morans_i)
Two zero-padded 2-D convolutions (scipy.signal.convolve2d,
mode='same', boundary='fill'):
WZ = conv2(z, W, 'same') # weighted sum of neighbours
nS = conv2(ones_like(z), W, 'same') # normaliser, edge-correction
lagged = WZ / nS # mean of standardised neighbours
local_i = z * lagged # Local Moran's I per pixel
The nS normaliser corrects for kernel weight that "falls off the edge" of
the image, which is why we use zero-padding rather than a periodic boundary.
4. Global Moran's I (global_morans_i)
The global statistic is the OLS slope of lagged regressed on z —
i.e. cov(z, lagged) / var(z). Computed with numpy.polyfit. Returns
0.0 when z has zero variance.
5. Permutation test (_permutation_pass, inside morans_compute)
For each repetition 1…n_repeats:
- Shuffle
zto obtainz_perm. - Recompute the permuted lagged map and
local_i_perm. - Sign-aware tally: for each pixel, increment a counter when
local_i_permlies in the same tail (positive vs. negative) as the observedlocal_i_obsand has equal-or-greater magnitude.
After n_repeats permutations, the pseudo p-value per pixel is
(count + 1) / (n_repeats + 1) — the standard +1 correction that keeps
p strictly positive. Sign-aware tallying matches the MATLAB reference
and is not equivalent to a two-sided absolute-value test.
6. Cluster classification (classify_clusters)
Threshold the p-values at the user-chosen significance level, then
classify each significant pixel by the signs of z and lagged:
z |
lagged |
label | code |
|---|---|---|---|
| ≥ 0 | ≥ 0 | HH | 1 |
| < 0 | < 0 | LL | 2 |
| < 0 | ≥ 0 | LH | 3 |
| ≥ 0 | < 0 | HL | 4 |
Non-significant pixels get code 0 (NS).
Threading model
The whole pipeline is exposed as a generator (morans_compute) that yields
integer progress percentages in [0, 100]. The widget wraps it in
@napari.qt.threading.thread_worker:
@thread_worker
def _run():
result = yield from morans_compute(image, order=…, sig_level=…, n_repeats=…)
return result
yield from automatically forwards every progress integer to the worker's
yielded Qt-signal (which drives the progress bar) and forwards the final
return value to the returned signal (which adds the output layers).
Cancel calls worker.quit(), which stops the generator at its next
yield, leaving the viewer responsive at all times.
Citation
If you use this plugin in academic work, please cite both the original paper
Dávid Cs. et al. (2024). eLife, doi:10.7554/eLife.89361.1
and napari (Sofroniew et al., 2022, doi:10.5281/zenodo.3555620).
Contributing
Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request.
License
Distributed under the terms of the BSD-3 license, "napari-morans-i" is free and open source software
Issues
If you encounter any problems, please file an issue along with a detailed description.
Version:
- 1.0
Last updated:
- 2026-05-09
First released:
- 2026-05-09
License:
- BSD-3-Clause
Supported data:
- Information not submitted
Plugin type:
Open extension:
Save extension:

