# ๐ŸŒด Palm Oil Ripeness Detection Service A **NestJS** backend API that uses an **ONNX-based YOLOv26 model** to perform real-time ripeness classification of oil palm fresh fruit bunches (FFB). Detection results are mapped against **MPOB (Malaysian Palm Oil Board) grading standards**, persisted to a local SQLite database, and served via a REST API. --- ## ๐Ÿš€ Features - **AI Inference via ONNX Runtime** โ€” Runs a custom-trained YOLOv8 model (`best.onnx`) using `onnxruntime-node` for zero-dependency, high-performance server-side inference. - **MPOB-Standard Classification** โ€” Detects and classifies palm oil bunches into 6 industry-standard grades: | Class | Description | |---|---| | `Ripe` | Optimal harvest quality | | `Underripe` | Harvested too early | | `Unripe` | Not yet ready | | `Overripe` | Past optimal harvest window | | `Abnormal` | โš ๏ธ Health alert | | `Empty_Bunch` | โš ๏ธ Health alert | - **Industrial Summary** โ€” Each analysis response includes a per-class count summary for field reporting. - **Health Alert Flagging** โ€” Detections with `Abnormal` or `Empty_Bunch` classes are automatically flagged with `is_health_alert: true`. - **History Persistence** โ€” All scans are saved to a local **SQLite** database via TypeORM, with the last 50 records retrievable via the history endpoint. - **CORS Enabled** โ€” Ready for integration with any frontend (Angular, React, etc.). - **Image Pass-Through** โ€” The original image is returned as a Base64 data URI in the response for frontend canvas rendering. --- ## ๐Ÿ—๏ธ Architecture Overview ``` src/ โ”œโ”€โ”€ main.ts # Bootstrap (port 3000, CORS enabled) โ”œโ”€โ”€ app.module.ts # Root module โ””โ”€โ”€ palm-oil/ โ”œโ”€โ”€ palm-oil.controller.ts # REST endpoints โ”œโ”€โ”€ palm-oil.service.ts # Orchestration logic & SQLite persistence โ”œโ”€โ”€ palm-oil.module.ts # Feature module โ”œโ”€โ”€ providers/ โ”‚ โ””โ”€โ”€ scanner.provider.ts # ONNX inference pipeline (preprocess โ†’ infer โ†’ postprocess) โ”œโ”€โ”€ entities/ โ”‚ โ””โ”€โ”€ history.entity.ts # TypeORM entity for scan history โ”œโ”€โ”€ interfaces/ โ”‚ โ””โ”€โ”€ palm-analysis.interface.ts # TypeScript types for API response โ””โ”€โ”€ constants/ โ””โ”€โ”€ mpob-standards.ts # MPOB class map, grade colors, and health alert list ``` ### Inference Pipeline (`ScannerProvider`) 1. **Preprocess** โ€” Resize image to `640ร—640` using `sharp`, strip alpha, extract raw pixels, and transpose from `HWC โ†’ CHW` layout, normalizing to `[0.0, 1.0]`. 2. **Inference** โ€” Feed the `[1, 3, 640, 640]` float tensor into the ONNX session. Input key: `images`. 3. **Postprocess** โ€” Parse the `[1, N, 6]` output tensor (`x1, y1, x2, y2, confidence, class_index`). Filter by a default confidence threshold of `0.25`. Scale normalized bounding box coordinates to the original image pixel dimensions. --- ## ๐Ÿ“‹ Prerequisites - **Node.js** `>=18` - **npm** `>=9` - The ONNX model file `best.onnx` must be placed in the **project root directory**. --- ## โš™๏ธ Project Setup ```bash npm install ``` --- ## โ–ถ๏ธ Running the Service ```bash # Development (single run) npm run start # Development (watch mode โ€” auto-restarts on file changes) npm run start:dev # Production npm run start:prod ``` The server starts on **`http://localhost:3000`** by default. Set the `PORT` environment variable to override. --- ## ๐Ÿ“ก API Endpoints ### `POST /palm-oil/analyze` Analyzes an uploaded image for palm oil ripeness. **Request:** `multipart/form-data` | Field | Type | Description | |---|---|---| | `image` | `File` | The palm oil bunch image to analyze | **Response:** `application/json` ```json { "status": "success", "current_threshold": 0.25, "total_count": 4, "industrial_summary": { "Empty_Bunch": 0, "Underripe": 1, "Abnormal": 0, "Ripe": 2, "Unripe": 0, "Overripe": 1 }, "detections": [ { "bunch_id": 1, "class": "Ripe", "confidence": 0.9312, "is_health_alert": false, "box": [120.5, 88.3, 450.2, 390.1] } ], "image_data": "data:image/jpeg;base64,...", "inference_ms": 42.15, "processing_ms": 115.30, "archive_id": "palm_1712345678901_456" } ``` --- ### `GET /palm-oil/history` Returns the last 50 scan records from the SQLite database, ordered by most recent first. **Response:** `application/json` โ€” Array of `History` entities. --- ## ๐Ÿงช Running Tests ```bash # Unit tests npm run test # Unit tests (watch mode) npm run test:watch # End-to-end tests npm run test:e2e # Test coverage report npm run test:cov ``` --- ## ๐Ÿ—„๏ธ Database Scan history is persisted to a local **SQLite** file (`palm_history.db`) in the project root, managed by **TypeORM** with `synchronize: true`. No external database setup is required. Each history record stores: - `archive_id` โ€” Unique scan identifier - `filename` โ€” Original uploaded file name - `total_count` โ€” Number of detections - `industrial_summary` โ€” Per-class count (JSON) - `detections` โ€” Full detection array with bounding boxes (JSON) - `inference_ms` / `processing_ms` โ€” Performance timings - `created_at` โ€” Auto-generated timestamp --- ## ๐Ÿ—‚๏ธ Key Files | File | Purpose | |---|---| | `best.onnx` | YOLOv8 ONNX inference model (must be in project root) | | `palm_history.db` | SQLite scan history database (auto-created) | | `src/palm-oil/constants/mpob-standards.ts` | MPOB class definitions, colors, and health alert flags | | `src/palm-oil/providers/scanner.provider.ts` | Core AI inference pipeline | | `src/palm-oil/palm-oil.service.ts` | Business logic, summary generation, persistence | | `src/palm-oil/palm-oil.controller.ts` | REST API layer | --- ## ๐Ÿ“ฆ Core Dependencies | Package | Purpose | |---|---| | `@nestjs/core` | NestJS framework | | `onnxruntime-node` | ONNX model inference | | `sharp` | High-performance image preprocessing | | `typeorm` + `sqlite3` | Database ORM and SQLite driver | | `class-validator` | DTO validation | --- ## ๐Ÿ“„ License UNLICENSED โ€” Private project.