VIN scanning from the "Add Vehicle" screen failed on all images with "No VIN Pattern found in image". Root cause: Tesseract fragments VINs into multiple words (e.g., "1HGBH 41JXMN 109186") but candidate extraction required a continuous 17-char sequence, rejecting everything.
Changes
Fix candidate extraction (vin_validator.py): Two-strategy approach -- continuous run matching + sliding window concatenation of adjacent OCR fragments
Replace filesystem-based debug system (VIN_DEBUG_DIR) with standard
logger.debug() calls that flow through Loki when LOG_LEVEL=DEBUG.
Use .env.logging variable for OCR LOG_LEVEL. Increase image capture
quality to 0.95 for better OCR accuracy.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Save original, adaptive, and Otsu preprocessed images to
/tmp/vin-debug/{timestamp}/ when LOG_LEVEL is set to debug.
No images saved at info level. Volume mount added for access.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
White text on green VIN stickers has only ~12% contrast in standard
grayscale conversion because the green channel dominates luminance.
The new _best_contrast_channel method evaluates each RGB channel's
standard deviation and selects the one with highest contrast, giving
~2x improvement for green-tinted VIN stickers. Falls back to standard
grayscale for neutral-colored images.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace std-based channel selection (which incorrectly picked green for
green-tinted VIN stickers) with per-pixel min(B,G,R). White text stays
255 in all channels while colored backgrounds drop to their weakest
channel value, giving 2x contrast improvement. Add morphological
opening after thresholding to remove noise speckles from car body
surface that were confusing Tesseract's page segmentation.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Two fixes:
1. Always use min-channel for color images instead of gated comparison
that was falling back to standard grayscale (which has only 23%
contrast for white-on-green VIN stickers).
2. Add grayscale-only OCR path (CLAHE + denoise, no thresholding)
between adaptive and Otsu attempts. Tesseract's LSTM engine is
designed to handle grayscale input directly and often outperforms
binarized input where thresholding creates artifacts.
Pipeline order: adaptive threshold → grayscale-only → Otsu threshold
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The min-channel correctly extracts contrast (white text=255 vs green
sticker bg=130), but Tesseract expects dark text on light background.
Without inversion, the grayscale-only path returned empty text for
every PSM mode because Tesseract couldn't see bright-on-dark text.
Invert via bitwise_not: text becomes 0 (black), sticker bg becomes
125 (gray). Fixes all three OCR paths (adaptive, grayscale, Otsu).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
tessedit_char_whitelist does not work with OEM 1 (LSTM engine) and
causes empty/erratic output. This was the root cause of Tesseract
returning empty text despite clear, well-preprocessed images.
Character filtering is already handled post-OCR by the VIN validator's
correct_ocr_errors() method (I->1, O->0, Q->0, etc).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
When OCR reads extra characters (e.g. sticker border as 'C', spurious
'Z' insertion), the raw text exceeds 17 chars and the old first-17
trim produced wrong VINs. New strategy tries all 17-char sliding
windows and single/double character deletions, validating each via
check digit. For 'CWVGGNPE2Z4NP069500', this finds the correct VIN
'WVGGNPE24NP069500' (valid check digit) instead of 'CWVGGNPE2Z4NP0695'
(invalid).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #113
Summary
VIN scanning from the "Add Vehicle" screen failed on all images with "No VIN Pattern found in image". Root cause: Tesseract fragments VINs into multiple words (e.g., "1HGBH 41JXMN 109186") but candidate extraction required a continuous 17-char sequence, rejecting everything.
Changes
vin_validator.py): Two-strategy approach -- continuous run matching + sliding window concatenation of adjacent OCR fragmentsvin_extractor.py): Disable dictionaries, set LSTM engine (OEM 1)vin_extractor.py): Sparse text and raw line modes for difficult imagesvin_preprocessor.py): Upscale images below 600px width for Tesseract accuracyvin_preprocessor.py): Alternative preprocessing fallback when adaptive thresholding failsvin_validator.py): Remove incorrect B->8 and S->5 mappings (valid VIN chars)Test Plan
Save original, adaptive, and Otsu preprocessed images to /tmp/vin-debug/{timestamp}/ when LOG_LEVEL is set to debug. No images saved at info level. Volume mount added for access. Co-Authored-By: Claude Opus 4.6 <[email protected]>