- 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)
70 lines
3.8 KiB
Markdown
70 lines
3.8 KiB
Markdown
# 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.
|