Extend metadata-only bucketing to still images in the discovery backend - #3001
Extend metadata-only bucketing to still images in the discovery backend#3001AL3708 wants to merge 1 commit into
Conversation
Metadata-only bucketing already exists, but not for still images scanned from a local directory. Videos skip decoding via ffprobe, and the parquet, huggingface and webshart backends read dimensions from metadata columns. With the discovery backend on a plain image folder, every file is fully read and decoded just to reach image.size. Bucketing never needs those pixels: calculate_target_size() and the crop coordinate maths derive everything from original_size, and PreparedSample stores no pixel-derived metadata. On a network filesystem this means a multi-megabyte read per image for data that is discarded immediately, and the only way to avoid it today is to maintain an external parquet of dimensions the file headers already contain. Still images now take the same metadata-only path as videos. use_metadata_only is already supported downstream -- TrainingSample tolerates image=None, since crop() and the transform helper both guard on `self.image is not None`, and meets_resolution_requirements() has an image_metadata branch reading original_size. So this adds the entry point plus a header probe. _probe_image_dimensions() opens the file with PIL, which parses only the header; pixel data is loaded lazily and never requested. The two per-media guards are folded into one _should_use_metadata_only(is_video_file), so the face-crop exclusion lives in one place instead of being duplicated. is_video_file is passed explicitly so a video-extension file inside an IMAGE dataset still falls back to a full decode. Verified equivalent to the previous logic across all 480 combinations of file kind, dataset type, ffprobe availability, backend type, crop flag and crop style. Guards follow the existing video precedent: local backends only, IMAGE and CONDITIONING dataset types, never with crop_style="face" (the one crop style whose coordinates depend on pixel content), and any header read failure falls back to a full decode. EXIF orientations 5-8 transpose the image, so header dimensions are swapped relative to exif_transpose() on the decode path. The orientation tag is in the header, so it is applied here. Verified equal to the full-decode result for all eight orientations plus JPEG/PNG/WebP without EXIF, with corrupt headers falling back as intended.
|
unfortunately, PIL is really slow for decode versus TrainingSample which uses OpenCV under the hood. can you place this path through tsr instead? might need to open a PR to the TrainingSample repo to add a probe helper. |
|
Hi,
Where a decode genuinely is needed, nothing changes: the probe returns That makes this one-directional — it can only speed things up, never slow them down. On the hit path a full read+decode becomes a header read. On the fallback path the only added cost is that header read itself before the original code runs, which is negligible next to the decode that follows. There's no configuration where this does more work than before. |
|
well, one thing that the full decode would achieve is to allow us to delete corrupted samples if eg. |
Context
Metadata-only bucketing already exists in SimpleTuner — but not for still
images scanned from a local directory.
Where it does exist:
discovery.pyskips decoding videos via ffprobe(
_should_use_metadata_only_for_video→use_metadata_only = True)parquetbackend readswidth_column/height_columnhuggingfacebackend reads width/height dataset columnswebshartbackend reads dimensions from shard metadataThe gap: with the
discoverybackend on a plain local image folder — the mostcommon setup — every image is fully read and decoded just to reach
image.size:Bucketing never needs those pixels.
calculate_target_size()and thecrop-coordinate maths in
TrainingSamplederive everything fromoriginal_size, andPreparedSamplestores no pixel-derived metadata. On anetwork filesystem this turns discovery into a multi-megabyte read per image
for data that is immediately discarded — and the only way to avoid it today is
to build and maintain an external parquet/JSONL of dimensions the file headers
already contain.
Change
Still images now take the same metadata-only path as videos, reading
dimensions from the file header.
use_metadata_onlyalready supports this downstream —TrainingSampletolerates
image=None:crop()guards its pixel work onself.image is not None and isinstance(self.image, Image.Image)self.image is not Nonemeets_resolution_requirements()has animage_metadatabranch readingoriginal_sizeSo this only adds the entry point, plus a header probe.
The two per-media guards are folded into one
_should_use_metadata_only(is_video_file), so the face-crop exclusion lives ina single place rather than being duplicated. Videos keep the ffprobe route;
images take the header route.
is_video_fileis passed explicitly so avideo-extension file inside an
IMAGEdataset still falls back to a fulldecode, exactly as before.
_probe_image_dimensions()opens the file with PIL, which parses only theheader — pixel data is loaded lazily and never requested.
Guards, following the existing video precedent:
IMAGEandCONDITIONINGdataset typescrop_style="face"— the one crop stylewhose coordinates depend on pixel content
Noneand falls back to a full decode, sotruncated, corrupt or unsupported files behave as before
EXIF correctness
EXIF orientations 5–8 transpose the image, so header dimensions are swapped
relative to what
exif_transpose()yields on the decode path. The orientationtag is in the header too, so it is applied in the probe.
Verified equal to the full-decode result across:
None→ fallback