# Botanica - Plant Identification iOS App ## Overview Custom iOS 17+ app using SwiftUI that identifies plants via camera with care schedules. **Stack:** - On-device ML: PlantNet-300K model converted to Core ML - Online API: Pl@ntNet (my.plantnet.org) for higher accuracy - Care data: Trefle API (open source botanical database) - Architecture: Clean Architecture + MVVM --- ## Phase 1: Foundation (Week 1-2) **Goal:** Core infrastructure with camera capture | Task | Description | |------|-------------| | 1.1 | Create Xcode project (iOS 17+, SwiftUI) | | 1.2 | Set up folder structure (App/, Core/, Domain/, Data/, ML/, Presentation/) | | 1.3 | Implement `DIContainer.swift` for dependency injection | | 1.4 | Create domain entities: `Plant`, `PlantIdentification`, `PlantCareSchedule`, `CareTask` | | 1.5 | Define repository protocols in `Domain/RepositoryInterfaces/` | | 1.6 | Build `NetworkService.swift` with async/await and multipart upload | | 1.7 | Implement `CameraView` + `CameraViewModel` with AVFoundation | | 1.8 | Set up Core Data stack for persistence | | 1.9 | Create tab navigation (Camera, Collection, Care, Settings) | **Deliverable:** Working camera capture with photo preview --- ## Phase 2: On-Device ML (Week 3-4) **Goal:** Offline plant identification with Core ML | Task | Description | |------|-------------| | 2.1 | Download PlantNet-300K pre-trained ResNet weights | | 2.2 | Convert to Core ML using `coremltools` (Python script) | | 2.3 | Add `PlantNet300K.mlpackage` to Xcode | | 2.4 | Create `PlantLabels.json` with 1,081 species names | | 2.5 | Implement `PlantClassificationService.swift` using Vision framework | | 2.6 | Create `ImagePreprocessor.swift` for model input normalization | | 2.7 | Build `IdentifyPlantOnDeviceUseCase.swift` | | 2.8 | Create `IdentificationView` showing results with confidence scores | | 2.9 | Build `SpeciesMatchCard` and `ConfidenceIndicator` components | | 2.10 | Performance test on device (target: <500ms inference) | **Deliverable:** End-to-end offline identification flow --- ## Phase 3: PlantNet API Integration (Week 5-6) **Goal:** Hybrid identification with API fallback | Task | Description | |------|-------------| | 3.1 | Register at my.plantnet.org for API key | | 3.2 | Create `PlantNetEndpoints.swift` (POST /v2/identify/{project}) | | 3.3 | Implement `PlantNetAPIService.swift` with multipart image upload | | 3.4 | Create DTOs: `PlantNetIdentifyResponseDTO`, `PlantNetSpeciesDTO` | | 3.5 | Build `PlantNetMapper.swift` (DTO → Domain entity) | | 3.6 | Implement `IdentifyPlantOnlineUseCase.swift` | | 3.7 | Create `HybridIdentificationUseCase.swift` (on-device first, API for confirmation) | | 3.8 | Add network reachability monitoring | | 3.9 | Handle rate limiting (500 free requests/day) | | 3.10 | Implement `IdentificationCache.swift` for previous results | **Deliverable:** Hybrid identification combining on-device + API --- ## Phase 4: Trefle API & Plant Care (Week 7-8) **Goal:** Complete care information and scheduling | Task | Description | |------|-------------| | 4.1 | Register at trefle.io for API token | | 4.2 | Create `TrefleEndpoints.swift` (GET /plants/search, GET /species/{slug}) | | 4.3 | Implement `TrefleAPIService.swift` | | 4.4 | Create DTOs: `TrefleSpeciesDTO`, `GrowthDataDTO` | | 4.5 | Build `TrefleMapper.swift` mapping growth data to care schedules | | 4.6 | Implement `FetchPlantCareUseCase.swift` | | 4.7 | Create `CreateCareScheduleUseCase.swift` | | 4.8 | Build `PlantDetailView` with `CareInformationSection` | | 4.9 | Implement `CareScheduleView` with upcoming tasks | | 4.10 | Add local notifications for care reminders | **Deliverable:** Full plant care data with watering/fertilizer schedules --- ## Phase 5: Plant Collection & Persistence (Week 9-10) **Goal:** Saved plants with full offline support | Task | Description | |------|-------------| | 5.1 | Define Core Data models (PlantMO, CareScheduleMO, IdentificationMO) | | 5.2 | Implement `CoreDataPlantStorage.swift` | | 5.3 | Build `PlantCollectionRepository.swift` | | 5.4 | Create use cases: `SavePlantUseCase`, `FetchCollectionUseCase` | | 5.5 | Build `CollectionView` with grid layout | | 5.6 | Implement `ImageCache.swift` for offline images | | 5.7 | Add search/filter in collection | **Deliverable:** Full plant collection management with offline support --- ## Phase 6: Polish & Release (Week 11-12) **Goal:** Production-ready application | Task | Description | |------|-------------| | 6.1 | Build `SettingsView` (offline mode toggle, API status, cache clear) | | 6.2 | Add comprehensive error handling with `ErrorView` | | 6.3 | Implement loading states with shimmer effects | | 6.4 | Add accessibility labels and Dynamic Type support | | 6.5 | Performance optimization pass | | 6.6 | Write unit tests for use cases and services | | 6.7 | Write UI tests for critical flows | | 6.8 | Final QA and bug fixes | **Deliverable:** App Store ready application --- ## Project Structure ``` Botanica/ ├── App/ │ ├── BotanicaApp.swift │ └── Configuration/ │ ├── AppConfiguration.swift │ └── APIKeys.swift ├── Core/ │ ├── DI/DIContainer.swift │ ├── Extensions/ │ └── Utilities/ ├── Domain/ │ ├── Entities/ │ │ ├── Plant.swift │ │ ├── PlantIdentification.swift │ │ ├── PlantCareSchedule.swift │ │ └── CareTask.swift │ ├── UseCases/ │ │ ├── Identification/ │ │ │ ├── IdentifyPlantOnDeviceUseCase.swift │ │ │ ├── IdentifyPlantOnlineUseCase.swift │ │ │ └── HybridIdentificationUseCase.swift │ │ ├── PlantCare/ │ │ │ ├── FetchPlantCareUseCase.swift │ │ │ └── CreateCareScheduleUseCase.swift │ │ └── Collection/ │ └── RepositoryInterfaces/ ├── Data/ │ ├── Repositories/ │ ├── DataSources/ │ │ ├── Remote/ │ │ │ ├── PlantNetAPI/ │ │ │ │ ├── PlantNetAPIService.swift │ │ │ │ └── DTOs/ │ │ │ ├── TrefleAPI/ │ │ │ │ ├── TrefleAPIService.swift │ │ │ │ └── DTOs/ │ │ │ └── NetworkService/ │ │ └── Local/ │ │ ├── CoreData/ │ │ └── Cache/ │ └── Mappers/ ├── ML/ │ ├── Models/ │ │ └── PlantNet300K.mlpackage │ ├── Services/ │ │ └── PlantClassificationService.swift │ └── Preprocessing/ ├── Presentation/ │ ├── Scenes/ │ │ ├── Camera/ │ │ ├── Identification/ │ │ ├── PlantDetail/ │ │ ├── Collection/ │ │ ├── CareSchedule/ │ │ └── Settings/ │ ├── Common/Components/ │ └── Navigation/ └── Resources/ ├── PlantLabels.json └── Assets.xcassets ``` --- ## API Details ### Pl@ntNet API - **Base URL:** `https://my-api.plantnet.org` - **Endpoint:** `POST /v2/identify/{project}` - **Free tier:** 500 requests/day - **Coverage:** 77,565 species - **Documentation:** [my.plantnet.org/doc](https://my.plantnet.org/doc) ### Trefle API - **Base URL:** `https://trefle.io/api/v1` - **Endpoints:** - `GET /plants/search?q={name}` - `GET /species/{slug}` - **Data:** Light requirements, watering, soil, temperature, fertilizer, growth info - **Documentation:** [docs.trefle.io](https://docs.trefle.io) --- ## Core ML Conversion ### Prerequisites ```bash pip install torch torchvision coremltools pillow numpy ``` ### Conversion Script ```python # scripts/convert_plantnet_to_coreml.py import torch import torchvision.models as models import coremltools as ct # Load PlantNet-300K pre-trained ResNet model = models.resnet50(weights=None) model.fc = torch.nn.Linear(model.fc.in_features, 1081) model.load_state_dict(torch.load("resnet50_weights_best_acc.tar", map_location='cpu')['state_dict']) model.eval() # Trace for conversion traced = torch.jit.trace(model, torch.rand(1, 3, 224, 224)) # Convert to Core ML image_input = ct.ImageType( name="image", shape=(1, 3, 224, 224), scale=1/255.0, bias=[-0.485/0.229, -0.456/0.224, -0.406/0.225], color_layout=ct.colorlayout.RGB ) mlmodel = ct.convert( traced, inputs=[image_input], convert_to="mlprogram", minimum_deployment_target=ct.target.iOS17, compute_precision=ct.precision.FLOAT16, ) mlmodel.save("PlantNet300K.mlpackage") ``` ### Download Weights ```bash # From Zenodo (PlantNet-300K official) wget https://zenodo.org/records/4726653/files/resnet50_weights_best_acc.tar ``` --- ## Key Data Models ### Plant Entity ```swift struct Plant: Identifiable, Sendable { let id: UUID let scientificName: String let commonNames: [String] let family: String let genus: String let imageURLs: [URL] let dateIdentified: Date let identificationSource: IdentificationSource enum IdentificationSource: String { case onDevice, plantNetAPI, hybrid } } ``` ### PlantCareSchedule Entity ```swift struct PlantCareSchedule: Identifiable, Sendable { let id: UUID let plantID: UUID let lightRequirement: LightRequirement let wateringSchedule: WateringSchedule let temperatureRange: TemperatureRange let fertilizerSchedule: FertilizerSchedule? let tasks: [CareTask] } ``` --- ## Verification Checklist | Test | Expected Result | |------|-----------------| | Camera capture | Take photo → preview displays | | On-device ML | Photo → top 10 species with confidence scores (<500ms) | | PlantNet API | Photo → API results match/exceed on-device accuracy | | Trefle API | Scientific name → care data (watering, light, fertilizer) | | Save plant | Save to collection → persists after app restart | | Offline mode | Disable network → on-device identification still works | | Care reminders | Create schedule → notification fires at scheduled time | --- ## Resources - [PlantNet-300K GitHub](https://github.com/plantnet/PlantNet-300K) - [PlantNet-300K Dataset (Zenodo)](https://zenodo.org/records/4726653) - [Pl@ntNet API Docs](https://my.plantnet.org/doc) - [Trefle API Docs](https://docs.trefle.io) - [Apple Core ML Tools](https://github.com/apple/coremltools) - [Vision Framework](https://developer.apple.com/documentation/vision)