Image rotation
Rotation predicts the correction needed to orient an image correctly and saves a corrected copy. In the core pipeline, rotation follows classification and prepares the selected images for parsing.
Rotate images from a directory
rotate_imgs()
takes a directory of images and saves correction angles and corrected copies
into one dedicated output directory.
from pathlib import Path
from flowde.classify_fns.classify_types import RotationClassification
from flowde.classify_fns.openai_classify_fn import make_openai_classify_fn
from flowde.rotate_imgs import rotate_imgs
img_dir = Path("results/classification/positive_images")
save_dir = Path("results/rotation")
rotation_fn = make_openai_classify_fn(
input_text=(
"Return the clockwise angle needed to orient this image correctly, "
"so the majority of text reads left to right and top to bottom. "
"Return one of 0, 90, 180 or 270 degrees."
),
model="gpt-5.6-luna",
effort="medium",
result_structure=RotationClassification,
)
angles = rotate_imgs(
classify_fn=rotation_fn,
img_dir=img_dir,
save_dir=save_dir,
max_concurrent_jobs=1,
)
rotate_imgs()
processes *.png files inside img_dir, in sorted path order.
Subdirectories are not searched. The returned angles list contains one
correction angle per processed image: angles[0] corresponds to the first
sorted image path, angles[1] to the second sorted image path, and so on.
The example above uses OpenAI and requires the
OpenAI setup.
For classify_fn, you can use an OpenAI classifier created with
make_openai_classify_fn(),
a Gemini classifier created with
make_gemini_classify_fn(),
or your own rotation classifier.
The provider setup guide also covers
Azure OpenAI.
The example sets result_structure to
RotationClassification
to restrict the classifier's labels to 0, 90, 180 or 270. These labels
specify clockwise corrections in degrees: 90 means rotate the input image
clockwise by 90 degrees.
You can also rotate extracted images before classification by using the
extraction output directory as img_dir.
Note: concurrency
max_concurrent_jobs controls how many calls to classify_fn can run
concurrently and defaults to 10. Parallel execution in
rotate_imgs()
uses threads, allowing concurrency to greatly exceed the CPU core count
when waiting for API responses. Choose max_concurrent_jobs based on your
API limits.
See the
rotate_imgs()
and
make_openai_classify_fn()
API references for full details of all parameters.
Rotation results
Flowde saves the correction angles and corrected image copies:
results/rotation/
├── rotations.json
├── rotated_images/
│ ├── paper-1_0.png
│ └── paper-2_0.png
└── .flowde/
rotations.json records the input image path and correction angle:
[
{
"img_path": "results/classification/positive_images/paper-1_0.png",
"label": 0
},
{
"img_path": "results/classification/positive_images/paper-2_0.png",
"label": 90
}
]
Every successfully processed image has a copy in
save_dir / "rotated_images", including images labelled 0 that need no
rotation. The copies retain their filenames, and the original images in
img_dir remain unchanged.
.flowde records which images have been processed, their correction
angles, the rotation settings and file fingerprints. Flowde uses this metadata
to resume the run and detect changes to the input images or saved results.
Resume rotation
To continue an unfinished rotation run whose results are saved in save_dir,
you can set on_existing="resume":
angles = rotate_imgs(
classify_fn=rotation_fn,
img_dir=img_dir,
save_dir=save_dir,
max_concurrent_jobs=1,
on_existing="resume",
)
The .flowde directory inside save_dir stores the rotation run's state.
Flowde uses these records to resume unfinished work and check the integrity of the run.
Resume raises an error if the classifier's settings have changed, or if
previously processed input images or saved outputs have been edited.
To use different settings, you can start a run in a new save_dir or replace
the previous run with on_existing="overwrite". See
managing runs for the full rules.
Bring your own rotation function
You can write your own rotation classifier and pass it to
rotate_imgs()
as classify_fn. See the
custom rotation tutorial
for the requirements your function must meet and a complete working example.
Next step
You can use save_dir / "rotated_images" as the input directory for
image parsing.