Files
plant-disease-id/tasks/hyper-specific-plant-disease-id/03-image-upload-and-preprocessing.md
Michael Freno 820a872f07 Initial commit: Plant Disease Identification app
- Next.js 16 App Router project with Tailwind CSS
- Plant disease knowledge base (93 diseases, 25 plants)
- Image upload with client+server preprocessing
- ML inference pipeline with mock/demo fallback
- Responsive results page with disease cards and treatment
- Full test suite (285 passing tests)
2026-06-05 19:21:16 -04:00

3.8 KiB
Raw Blame History

03. Image Upload Component and Preprocessing Pipeline

meta: id: hyper-specific-plant-disease-id-03 feature: hyper-specific-plant-disease-id priority: P1 depends_on: [hyper-specific-plant-disease-id-01] tags: [frontend, image-processing, component]

objective:

  • Build a drag-and-drop / file-picker image upload component with client-side preview, validation (size, type, dimensions), and a preprocessing pipeline that resizes, normalizes, and formats images for the ML inference engine.

deliverables:

  • components/ImageUpload.tsx — drop zone + file picker with preview thumbnail, progress indicator, and error messages
  • lib/image-processing.ts — client-side image preprocessing: resize to 224×224, convert to RGB tensor (Float32Array), normalize to model-expected range, return as tensor or base64
  • app/api/upload/route.ts — server endpoint that accepts multipart image, runs preprocessing server-side, and returns { imageId, tensorShape, previewUrl }
  • lib/api/upload.ts — client helper to POST image and get back image metadata

steps:

  1. Build components/ImageUpload.tsx:
    • Drag-and-drop zone with dashed border and "drop here" / "click to browse" states.
    • File picker accept image/* (PNG, JPG, WebP) with max 10 MB client-side check.
    • Preview thumbnail rendered as <img> from URL.createObjectURL().
    • Clear / retry button.
    • Loading spinner during upload.
    • Inline error display (wrong type, too large, upload failed).
  2. Implement lib/image-processing.ts:
    • resizeImage(file: File, size: number): Promise<ImageData> — draws to offscreen canvas, bilinear resize to 224×224.
    • imageToTensor(imageData: ImageData): Float32Array — converts RGBA to RGB, normalizes to [0,1] or model-specific range, returns flat Float32Array.
    • tensorToBase64(tensor: Float32Array): string — for transmitting to server.
  3. Build app/api/upload/route.ts:
    • Accept multipart/form-data with field image.
    • Validate MIME type, file size (10 MB limit), and minimum dimensions (150×150).
    • Save uploaded file to public/uploads/{uuid}.{ext}.
    • Run server-side preprocessing pipeline (same resize + normalize logic).
    • Return { imageId: uuid, tensorShape: [1, 3, 224, 224], previewUrl: "/uploads/{uuid}.{ext}" }.
    • Cleanup: ephemeral uploads (keep last 100, purge older via cron or on-demand).
  4. Wire ImageUpload component to call lib/api/upload.ts on file selection.
  5. Add loading skeleton while upload is in progress.
  6. Test with sample images of various sizes, types, and orientations.

tests:

  • Unit: resizeImage() produces 224×224 output for any input aspect ratio.
  • Unit: imageToTensor() output length equals 3 * 224 * 224.
  • Unit: Normalization produces values in [0, 1] range.
  • Integration: Upload a valid JPG → POST /api/upload returns 200 with expected shape.
  • Integration: Upload a 12 MB file → returns 413 or validation error.
  • Integration: Upload a .txt file → returns 400 with MIME error.

acceptance_criteria:

  • User can drag-and-drop or click to select an image.
  • Preview thumbnail shows before upload.
  • Upload progress indicator shows.
  • Server returns imageId and previewUrl.
  • Non-image files are rejected with clear message.
  • Images >10 MB are rejected.
  • Output tensor has shape [1, 3, 224, 224].

validation:

# Upload a sample plant photo
curl -X POST -F "image=@test-assets/tomato-leaf.jpg" http://localhost:3000/api/upload
# → {"imageId":"uuid","tensorShape":[1,3,224,224],"previewUrl":"/uploads/..."}

notes:

  • The tensor shape [1, 3, 224, 224] matches standard MobileNet / ResNet input expectations.
  • If the user picks a custom model architecture later, the shape constants in lib/image-processing.ts should be configurable via env var.
  • Uploaded files are ephemeral — stored in public/uploads/ which is gitignored.