smart-crop-ai
Crop to the interesting part of an image instead of the middle of it — and get a confidence number that tells you when the crop is barely better than a guess.
Install
pip install smart-crop-ai
Quickstart
import numpy as np, smart_crop_ai
from PIL import Image
wall = np.full((400, 800, 3), 200, dtype=np.uint8) # a plain wall
subject = np.random.default_rng(0).integers(0, 255, (140, 140, 3)) # something worth keeping
wall[140:280, 560:700] = subject.astype(np.uint8) # off to the right
result = smart_crop_ai.crop(Image.fromarray(wall), ratio=(1, 1))
print(result.summary())
smart-crop-ai: <image>
source 800 x 400 pixels
asked for 400 x 400 pixels
crop 400 x 400 at (328, 0), box (328, 0, 728, 400)
strategy saliency (asked for: auto)
confidence 0.69 strong - a centre crop of the same size would have missed 69% of the detail this window keeps
keeps 50% of the source area, off centre
result.image is the cropped PIL.Image. A centre crop would have sliced the
subject in half; this one contains it, and says by how much it won.
What it does
- Finds the subject. Every window of the size you asked for is scored against an energy map and the best one wins. No model, no download, no OpenCV.
- Tells you when it did not.
confidenceis the share of the chosen window's detail that a plain centre crop would have missed. At 0, cropping here bought you nothing, andnotessays why. - Refuses to bluff. A blank wall, a gradient, a sky — anything with a flat energy map falls back to the centre and records that it did, instead of maximising sensor noise and calling it a subject.
- Says which of the two it was. Confidence 0 has two very different causes,
and
notesnever confuses them: either the image holds no detail anywhere, or it holds plenty but your crop is so large that every placement contains the same thing. The second one tells you to ask for a smaller crop. - Never upscales behind your back. Ask for a crop bigger than the source and you get the whole image, confidence 0, and a note saying so.
- Takes any image Pillow can open. Greyscale, RGBA, palette, 16-bit and CMYK all work. EXIF orientation is applied before anything is measured, so the box refers to the upright image.
- Leaves your image alone. The input is never modified, and the same input always produces the same box.
API
crop(image, width=None, height=None, *, ratio=None, strategy="auto", padding=0.05) -> CropResult
Give width and height, or a ratio such as (16, 9), 1.0 or "16:9" —
not both, which raises ValueError. A single width or height keeps the
source's aspect ratio. image is a PIL.Image, a path, or a numpy array.
| strategy | what it measures | when to reach for it |
|---|---|---|
"auto" |
saliency, falling back to the centre when the map is nearly flat | the default; use this |
"saliency" |
gradient magnitude plus local colour variance | general photographs |
"entropy" |
local Shannon entropy | texture, foliage, crowds |
"edges" |
Sobel edge density | product shots on plain backgrounds |
"center" |
nothing | a baseline to compare against |
padding (0 to 0.45) is breathing room: that fraction of each edge is left out
of the score, so the subject is framed inside the crop rather than pressed
against its border.
crop_to_file(src, dst, **kw) -> CropResult # crop and save; **kw goes to crop()
thumbnail(image, size, **kw) -> PIL.Image # crop to the subject, then resize to exactly size
thumbnail crops at the aspect ratio of size and then scales, so the subject
survives the shrink instead of landing wherever it lands.
CropResult
| attribute | what it holds |
|---|---|
.image |
the cropped PIL.Image.Image |
.box |
(left, top, right, bottom) in source pixels, always inside the image |
.strategy_used |
what actually chose the box: a strategy name, "center" on a flat image, or "whole_image" |
.confidence |
0 to 1 — how much the chosen window beat a centre crop |
.confidence_label |
"strong", "moderate", "weak" or "none" |
.notes |
anything you should know, in plain language |
.scores |
the raw window and energy numbers behind the decision |
.size, .offset, .covers, .moved |
convenience views of the box |
.summary() |
the human-readable report above |
.to_dict() / .to_json() |
JSON-safe, pixels left out |
.save(path) |
write the crop; alpha is flattened onto white for JPEG |
CLI
Nothing is written unless you ask, so you can look at the confidence first.
smart-crop-ai photo.jpg --ratio 16:9
smart-crop-ai photo.jpg --width 800 --height 600 --output hero.jpg
smart-crop-ai shots/ --ratio 1:1 --out-dir square/ --strategy edges --suffix -crop
smart-crop-ai shots/ --recursive --ratio 4:3 --quiet
smart-crop-ai photo.jpg --ratio 1:1 --json
--thumb WxH resizes after cropping, --report PATH writes the printed report
as UTF-8, and --min-confidence F exits with code 2 when any crop scores below
F — useful in a pipeline that would rather fail than ship a guess. Exit codes:
0 all good, 1 a usage or read error, 2 under --min-confidence.
License
MIT
Metadata
Release files for smart-crop-ai 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| smart_crop_ai-0.1.0.tar.gz | 36.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| smart_crop_ai-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.7 kB
Release files / smart_crop_ai-0.1.0.tar.gz
| Download URL | smart_crop_ai-0.1.0.tar.gz |
|---|---|
| Size | 36.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
736e7d4a6268e4e99b1ac4e442537e18a2741157aa329b1e6916b7db3be4fd87
|
|
BLAKE2b-256 checksum How to use checksums |
8d9247e9c0d3a82a3842f472f7484a2a00923126bddcdae991711dc15430e082
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / smart_crop_ai-0.1.0-py3-none-any.whl
| Download URL | smart_crop_ai-0.1.0-py3-none-any.whl |
|---|---|
| Size | 26.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a913585537f4f3e9adef177dca0826fa635056b5524c100ea9c7a343ee4b1589
|
|
BLAKE2b-256 checksum How to use checksums |
d86ebf2d8a8324b6a33e3f6d399739dab6d6b10c0b338ca38add46b03d21eb0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|