I’m happy to announce Regions.jl, a package that introduces a first-class region type for discrete 2D image analysis, now registered in the Julia General Registry.
What is a region?
A region is a set of discrete 2D pixel positions represented as a run-length encoding (RLE). Instead of storing or iterating over individual pixels, Regions.jl tracks contiguous vertical runs of active pixels. This seemingly simple representation unlocks two major advantages for machine vision workflows:
- Binary morphology without touching every pixel. Dilation, erosion, and related operations work on runs rather than on the full image array, yielding substantial speedups — on sparse industrial images, typically orders of magnitude faster than pixel-array approaches.
- Efficient blob analysis. Connected-component labeling and shape feature extraction (area, centroid, bounding box, …) all exploit the run-length structure, so computation scales with the number of runs, not with the image area.
Quick taste
using Regions, Images, FileIO
img = load("test/gear.png")
region = binarize(img, px -> px < 0.9)
blob = argmax(area, components(region)) # pick the largest connected component
l, t, r, b = bounds(blob) # bounding box
cc, cr = centroid(blob) # area-weighted centroid
No manual pixel loops needed — the region abstraction takes care of the bookkeeping.
Status
The package is at v0.5.0 and is MIT-licensed. Feedback, bug reports, and contributions are very welcome!
This looks really cool. Thank you for sharing.
Do Regions always start in column 1? As I read the docs, a Run is a starting row and a range of rows, and a Region is a vector or Runs. Why does the Region not have a starting column?
I think the use of Images.binarize is borderline type piracy: If Images.jl defines a method with Function on the second argument, there will be a conflict. You can prevent this by wrapping the function in a struct, e.g. replace binarize(img, px -> px > 0.5) with binarize(img, DefReg(px -> px > 0.5)) , where you define DefReg in Regions.jl as struct DefReg fun::Function end.
I’ve recently changed from row-major to column-major, and I might have missed something in the docs.
A Region consists of a sorted vector of Runs, each Run has a column and a range of rows; thus the starting column of a Region is the column of its first run.
Thanks for pointing out the binarize issue, i was feeling a bit uneasy also. I mean it is really doing the same thing and thus should be named the same. Maybe having both Images.binarize() and Regions.binarize() is not so bad, one would have to disambiguate with the module name. Still not decided what is the best solution.
A similar topic comes up with other names - like opening and closing - and here I have not yet captured the Images function.
A Region consists of a sorted vector of Runs, each Run has a column and a range of rows; thus the starting column of a Region is the column of its first run.
Thank you, I was reading it wrong.