- 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)
3.8 KiB
3.8 KiB
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 messageslib/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 base64app/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:
- 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>fromURL.createObjectURL(). - Clear / retry button.
- Loading spinner during upload.
- Inline error display (wrong type, too large, upload failed).
- 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.
- Build
app/api/upload/route.ts:- Accept
multipart/form-datawith fieldimage. - 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).
- Accept
- Wire
ImageUploadcomponent to calllib/api/upload.tson file selection. - Add loading skeleton while upload is in progress.
- 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 equals3 * 224 * 224. - Unit: Normalization produces values in [0, 1] range.
- Integration: Upload a valid JPG →
POST /api/uploadreturns 200 with expected shape. - Integration: Upload a 12 MB file → returns 413 or validation error.
- Integration: Upload a
.txtfile → 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.tsshould be configurable via env var. - Uploaded files are ephemeral — stored in
public/uploads/which is gitignored.