All notable changes to MultiClean are documented here.
0.5.0 - 2026-08-14
-
Edge smoothing no longer translates the output by one pixel down and to the right when
smooth_edge_sizeis even.cv2.morphologyEx(MORPH_OPEN)applies a single anchor to both the erosion and the dilation, which is only correct when that anchor coincides with the structuring element's centre of symmetry — true for odd kernel sizes, false for even ones. The result was a shifted opening that could also add pixels to a class rather than only removing them. The erosion and dilation are now run separately with the dilation anchored at the reflection of the erosion's anchor, giving a true opening at every size.This changes output for even
smooth_edge_size, including the default of2. Output for odd values is bit-identical to previous releases. If you have cached results produced with an evensmooth_edge_size, regenerate them or expect a one-pixel offset against new output. -
The source distribution no longer ships the example notebooks, their sample rasters, or the README artwork.
setuptools-scmhands the sdist every git-tracked file, so these were swept in automatically — 4.9 MB of a 7.2 MB tarball, none of it read by anything in the package. AMANIFEST.innow prunes them, taking the sdist to 3.9 MB. The notebooks remain on GitHub, where the README links them. Wheels were never affected and are unchanged.
0.4.0 - 2026-07-28
- Switched the OpenCV runtime dependency from
opencv-pythontoopencv-python-headless. MultiClean only usesmorphologyEx,connectedComponentsWithStatsanddistanceTransformWithLabels— no GUI or video I/O — so the GUI build was pulling in shared libraries that are absent from slim container and CI images, where plainopencv-pythonfails at import withlibGL.so.1: cannot open shared object file. Both distributions provide the samecv2module, so if your environment needs the fullopencv-pythonbuild for other work, install it explicitly; do not rely on both being present at once. - Raised the OpenCV floor to
>=4.10.opencv-pythonbuilds before 4.10.0.84 are compiled against NumPy 1.x and fail at import when paired with NumPy 2.x, which the previous>=4.0floor allowed a resolver to do.
- Declared
numpy>=1.21as an explicit runtime dependency. It was always imported directly and appears in the public type signatures, but was only installed transitively via OpenCV.
0.3.1 - 2026-07-21
clean_arrayno longer raisesKeyErrorwhenclass_valuesnames a class that does not occur in the input array. Requested classes absent from the array are now ignored, matching the behaviour that already applied whensmooth_edge_size=0. This mainly affected tiled processing, where a single fixed class list is reused across tiles whose contents vary — the failure depended on the data, so it surfaced intermittently.
0.3.0 - 2026-05-02
- Performance.
clean_arrayis substantially faster on multi-class inputs. On a 15669×18633 / 147-class land-use raster, end-to-end runtime dropped from ~85 s to ~40 s. On the 8011×7901 / 4-class Landsat cloud-and-shadow example, runtime dropped from ~2.5 s to ~1.1 s. Wins came from:- Replacing the float32 smoothed-labels buffer with a
uint8/uint16class-code array (selected automatically based on class count). The per-class equality scan is 2-4× cheaper in memory bandwidth. - Combining per-class small-island masks in flight instead of accumulating all K of them first.
- Filling invalid pixels in place rather than allocating a copy.
- Replacing
scipy.ndimage.distance_transform_edtwithcv2.distanceTransformWithLabelsfor the nearest-valid fill (~3.4× faster on the fill stage). Both algorithms produce mathematically equivalent output (the same minimum L2 distance); they differ only in which equidistant source pixel wins a tie.
- Replacing the float32 smoothed-labels buffer with a
- dtype preservation. The output now strictly matches the input dtype.
Previously the pipeline routed everything through float32 internally,
which silently downcast
float64inputs and roundedint32values larger than 2²⁴ (andint64values larger than 2⁵³).
- All-NaN float input with
fill_nan=Truenow deterministically returns an all-NaN array. The previous code relied on whatever valuenp.emptyhappened to leave in the sentinel slot. - Large integer class values (
int32> 2²⁴,int64> 2⁵³) are now preserved bit-exactly, instead of being silently rounded by the internal float32 round-trip.
- Dropped the
scipyruntime dependency.cv2(already a runtime dependency) now handles the distance-transform fill.
0.2.0 - 2025-09-03
fill_nanoption onclean_array: whenTrue, NaN values in float input arrays are filled from the nearest valid pixel rather than preserved as nodata.
0.1.0 - 2025-09-02
- Initial public release.
clean_arrayAPI for morphological cleaning of multi-class 2D arrays: per-class edge smoothing (morphological opening), per-class small-island removal (connected components), and gap filling using nearest-valid via Euclidean distance transform.- Documentation: README, two example notebooks (land use, cloud shadow), and a Google Colab tutorial notebook.