Bucket Ratio Crop
Stop feeding your model ratios it has never seen
- image
- mask
- data
- image
- width
- height
- bucket_label
What it's for
SD 1.5 and SDXL were trained on a fixed menu of sizes, and a 1920x1080 photo in the pipeline is how you get two heads and a stretched torso. The community's answer has been the same for years: "You are using a model trained on 1024x1024 images trying to make images with ridiculously different aspect ratios." Bucket Ratio Crop makes that impossible. Hand it an image, get back a crop at exactly one of the standard bucketing sizes - multiples of 64, the same tables Kohya and A1111 use - centered on whatever region it found.
Two jobs. Dataset prep for LoRA training, where multi-resolution bucketing is now the default and the trainer will crop your photos into buckets whether you like it or not: pre-cropping means you pick the framing. And subject work: cutting a person, or three, out of a photo at a native model ratio so an img2img pass has a frame it can resolve. Caveat worth stating: most current models take any size in a 1–2MP band and soften rather than break, so this is mostly an SD 1.5 / SDXL / Flux.1 concern, plus a training-dataset one - which is still most workflows people run.
How the buckets work
Three groups - 512, 768, 1024 - with 13, 17 and 25 ratios. Inside a group every side is a multiple of 64 and width + height is fixed: 1024, 1536 and 2048 respectively. The group number is the square bucket, and the square has the most area in its group; the further a ratio gets from square, the less area you get. 128 x 896 in the 512 group is 0.11MP against 512 x 512's 0.26MP. A group is a pixel budget, not a size.
It's a dense grid, not one model's trained set. The 1024 group has SDXL's 1024x1024, 1152x896 and 1216x832, but a 7:4 photo lands on 1280x768 where SDXL's bucket is 1344x768. Fine in the 1MP band; read bucket_label if you're fussy about a dataset.
The mechanism
Region detection has a silent priority order - the source is unambiguous:
data, if connected (Florence-2 bounding boxes)mask, if connectedbbox_x/bbox_y/bbox_width/bbox_height, if width and height are both above zero- otherwise the whole image
It expands that region by margin on every side, picks the bucket whose ratio is closest to the expanded region, drops buckets that don't fit inside your image, centers the crop on the region center, clamps to the frame, then resamples to the exact bucket dimensions. That resize is unconditional, which keeps every crop in a multi-detection batch the same size so they stack into one IMAGE batch. With several detections it picks one ratio per image, from the largest detection, and crops each one into that same batch; Save writes each item as its own file.
The inputs that matter
image- the source, and bigger is better (see below).bucket_group-512,768,1024, chosen by model era rather than taste.margin- default0.25, meaning 25% more on each side. The one you'll actually tune: 0.5+ for a tight headshot, less for a well-framed full-body shot.mask- any MASK, e.g. a BiRefNet or rembg cutout. It takes the mask's bounding box, not its outline, so you get a rectangle.data- Florence-2'sdataoutput. Kijai'sFlorence2Runwithcaption_to_phrase_groundingand the promptpersonis the intended pairing, and what the pack's own example workflow (workflows/bucket-crop-body.json) wires up.- the four
bbox_*ints - the manual, no-model path, for when you want a specific rectangle on-ratio.
Outputs: image (one crop, or a batch), width and height (the bucket dimensions), and bucket_label, a string like 768x448 (2 crops). Save for dataset work or VAE Encode for image, Empty Latent Image for width/height, a text display for bucket_label.
Installing it
ComfyUI Manager → search BucketCrop → install → restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/malcolmamal/ComfyUI-BucketCrop
# restart ComfyUI
That's all of it. pyproject.toml declares no dependencies, there's no requirements.txt, and nothing downloads a model - it's 423 lines of plain torch. Florence-2 weights are the Florence node's business, not this pack's. The repo's README is an empty file, so the tooltips are your documentation.
Where people get burned
data beats mask, silently. Wire both and your mask is ignored without a warning.
An empty mask means "whole image." An all-black mask doesn't error; it falls back to framing the entire photo at bucket ratio.
Batch indices. Florence-2's data is a list with one entry per image, indexed by position, and an out-of-range index falls back to entry [0]. Eight images with grounding data for one gives you the same detection set eight times.
Margin clamps at the frame edge. A subject touching the edge can't expand into space that isn't there, so you get an off-center crop instead of the roomy one you asked for.
"Never upscales" is slightly optimistic. The picker filters to buckets that fit your image, but on a tightly-framed source the crop can come out smaller than the bucket and get grown back. Feed it your largest source.
No crop offsets come out - image, width, height, bucket_label and nothing else - so if you need the same crop on a mask, do it upstream.
The workflow I'd actually build
Load Image Batch → Florence2Run (person, caption_to_phrase_grounding) → Bucket Ratio Crop at 1024 with margin around 0.3 → Save. Each detected person becomes its own on-ratio file, nothing stretched, nothing discarded.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| bucket_group | COMBO | 512 | 3 options: 512, 768, 1024 |
| margin | FLOAT | 0.250–2 | Expand detected region by this fraction on each side (0.25 = 25%) |
| maskopt | MASK | — | |
| dataopt | JSON | Connect Florence2Run 'data' output for per-person cropping | |
| bbox_xopt | INT | 00–16384 | — |
| bbox_yopt | INT | 00–16384 | — |
| bbox_widthopt | INT | 00–16384 | — |
| bbox_heightopt | INT | 00–16384 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| width | INT | — |
| height | INT | — |
| bucket_label | STRING | — |