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

70 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
# 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.