Automatic perspective correction was already working reasonably well by the time I started on Slides Thief 3.0. For ordinary lecture and presentation photos, the detector could usually find the projected slide, correct the perspective, and produce a usable result without much intervention.
The failures that remained were rarely complete breakdowns. More often, a boundary would land slightly inside the screen, two candidates would look similarly plausible, an edge would be too weak to support a confident choice, or one frame in a sequence would disagree with the rest of the batch.
Those cases exposed a different problem. Improving detection accuracy was still useful, but the application also needed a sensible way to handle results that were plausible without being trustworthy enough to ignore.
A confidence score alone did not provide that workflow.
Slides Thief is an open-source, local-first web app that straightens photographed lecture slides into clean PDFs. For the detection architecture, see Part 2: From Rectangle Heuristics to Evidence-Aware Review.
Turning Detection Signals Into Review Reasons
The detector already produced more information than a single confidence value. Candidate scoring considers signals such as edge support and continuity, geometry validity, aspect-ratio consistency, and agreement with batch-level patterns. It can also detect cases where several candidates are too close to distinguish cleanly.
The easiest UI would have been to expose the final scalar directly:
Confidence: 0.71
That number is useful when debugging the detector. It is much less useful when reviewing a deck.
A person looking at a slide does not really need to know whether the detector assigned it 0.71 or 0.74. They need to know what deserves attention.
Slides Thief therefore keeps the confidence value available, but review is driven by explicit reasons attached to the detection result. In 3.0, those reasons include:
Low detection confidence
Weak edge support
Multiple plausible candidate boundaries
Detected boundary outside the image
Inconsistency with the rest of the batch
Fallback frame used
The distinction matters because these conditions imply different kinds of inspection. A weak edge suggests checking the slide boundary. Ambiguous candidates suggest verifying that the detector chose the right region. A fallback frame means the automatic pipeline never found a satisfactory boundary at all.
The detector output consequently carries both a proposed quadrilateral and enough metadata for the application to decide whether that proposal should enter the normal path or the review path.
Treating Review as a Queue
A review badge is sufficient when a batch contains one questionable slide. It becomes less useful when several slides need attention.
Without a dedicated workflow, reviewing a larger batch means scanning the sidebar, opening one flagged slide after another, and remembering which ones have already been handled. The detector may have identified the uncertain items correctly, but the application has done little to help finish the job.
Version 3.0 turns those items into a review queue.
Entering review mode selects slides that still have needsReview set. Navigation moves through that subset rather than through the entire deck, and the interface keeps track of how many review items remain.
There are two ways for a slide to leave the queue.
If the automatic result is already correct, the user can explicitly accept it with Looks good. If the result is wrong, moving a corner converts the slide to manual geometry. The manual result becomes the active result and its automatic review flags are cleared.
Those two paths lead to the same practical outcome—the slide no longer needs review—but they retain different provenance. One says that the automatic proposal was accepted. The other says that the automatic proposal was replaced by a manual one.
That distinction turned out to be more useful than trying to model every review interaction as a separate confirmation step.
Using Export as the Review Boundary
Another question was when the application should interrupt the user.
Stopping detection every time an uncertain frame appears would make large imports painful. The useful behavior during import is usually to let the whole batch finish. At the other extreme, allowing export with no acknowledgement of unresolved slides would make the review system easy to miss.
The export action provided a natural boundary between the two.
Slides Thief can import a batch, run detection, and leave questionable results visible without blocking the rest of the workspace. When the user asks for an output artifact, unresolved review items become harder to ignore.
If review items remain, the export flow offers two choices: export anyway, or review the slides first.
Choosing review preserves the export format that initiated the flow. After the remaining review queue is resolved, the application can continue the pending PDF or JPG export instead of returning the user to a dead end where they have to remember what they were doing before review started.
The state transition is roughly:
This is a small amount of state to retain, but without it the review flow feels detached from the action that caused it.
Restoring a Detection Is Different From Running It Again
Manual editing introduced another ambiguity.
Suppose automatic detection produces quadrilateral A. The user then drags two corners and creates manual quadrilateral B.
At that point, “go back to automatic” can mean two different things.
The application can restore A, the automatic result that existed before the edit:
A → manual edit → B
B → Restore Auto-Detection → A
Or it can run detection again:
A → manual edit → B
B → Re-detect → detector → C
There is no guarantee that C is identical to A. Re-detection is a new computation. Restoration is not.
Slides Thief therefore keeps an automatic-detection snapshot with the slide. The snapshot contains the detected quad together with the method, confidence, review state, review reasons, and source ratio that belonged to that result.
Restore Auto-Detection copies that stored result back into the active slide state. Re-detect submits the image to the detection pipeline again.
Separating the two removed a surprisingly ambiguous “reset” operation from the editor. More importantly, it prevents a recoverable automatic result from being confused with a new detector run.
Reusing a Manual Quad Across Similar Photos
Slides are often photographed as a sequence. The camera position may stay nearly fixed while the projected content changes from one frame to the next.
That makes independent correction unnecessarily repetitive.
Version 3.0 allows the current quadrilateral to be applied to selected slides or subsequent slides. The coordinates cannot simply be copied as pixels because source images may have different dimensions.
Instead, each point is mapped through normalized image coordinates:
const u = x / sourceWidth;
const v = y / sourceHeight;
const targetX = u * targetWidth;
const targetY = v * targetHeight;
The implementation also clamps the adapted points to the target image bounds.
The important detail is what happens next: the adapted quadrilateral becomes the target slide’s manual geometry. Slides Thief does not feed it back into the detector as a new edge-search prior. The feature is intentionally simpler than that.
For a sequence photographed from a stable position, this can still remove a large amount of repetitive dragging. Correct one representative frame, propagate the geometry, and then inspect the exceptions.
That workflow also complements the separate batch-prior logic already used by automatic detection. One operates inside the detector; the other is an explicit user action. Keeping those mechanisms separate avoids pretending that a manual edit carries more statistical meaning than it actually does.
Keeping Existing Exports Honest After an Edit
Editable geometry creates one more state problem after export.
A user can generate a PDF or JPG archive and then continue editing the workspace. Once any relevant slide geometry or export setting changes, the artifact that was generated earlier no longer represents the current state.
There are two easy but unsatisfying responses.
The first is to delete the old artifact immediately. That is safe, but throws away a file the user may still want.
The second is to leave the artifact untouched with no indication that it is outdated. That preserves access, but makes it easy to download an export that no longer matches the visible workspace.
Slides Thief keeps the artifact and marks it as stale.
The download remains available, while the interface indicates that the workspace has changed since that artifact was generated. Regenerating the output replaces it with an artifact based on the current state.
There is no complicated provenance graph behind this. The application simply preserves an important distinction:
Artifact exists
≠
Artifact represents current workspace
That distinction became necessary only because 3.0 made the workspace substantially more editable after automatic processing had completed.
What Changed in 3.0
None of these individual ideas are unusual. Review queues, recoverable state, manual overrides, and uncertainty-aware interfaces all have established precedents.
What changed in Slides Thief was that uncertainty became part of the application state instead of remaining a property of the detector.
A detection result can now carry review reasons. A questionable result has somewhere to go. The user can accept the automatic proposal or replace it. The original proposal remains recoverable. A new detector run remains a separate operation. Similar manual geometry can be reused across a batch. Previously generated exports remain accessible without pretending they are current.
The detector still makes mistakes. Version 3.0 does not try to hide that fact.
It gives those mistakes somewhere to go.
Slides Thief Design Series
- Part 1: Turn Photographed Presentation Slides into Clean PDFs
- Part 2: Slides Thief 2.0: From Rectangle Heuristics to Evidence-Aware Review
- Part 3: Designing the Review Loop in Slides Thief (This post)
Try Slides Thief in the browser:
View the source code: