diff --git a/apps/web/drizzle/0002_add-prevalence.sql b/apps/web/drizzle/0002_add-prevalence.sql new file mode 100644 index 0000000..33c0310 --- /dev/null +++ b/apps/web/drizzle/0002_add-prevalence.sql @@ -0,0 +1,2 @@ +ALTER TABLE `diseases` ADD `prevalence` text DEFAULT 'uncommon' NOT NULL;--> statement-breakpoint +CREATE INDEX `idx_diseases_prevalence` ON `diseases` (`prevalence`); diff --git a/apps/web/drizzle/0003_giant_toad.sql b/apps/web/drizzle/0003_giant_toad.sql new file mode 100644 index 0000000..404f028 --- /dev/null +++ b/apps/web/drizzle/0003_giant_toad.sql @@ -0,0 +1,7 @@ +CREATE TABLE `plant_views` ( + `plant_id` text PRIMARY KEY NOT NULL, + `view_count` integer DEFAULT 0 NOT NULL, + FOREIGN KEY (`plant_id`) REFERENCES `plants`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `idx_plant_views_count` ON `plant_views` (`view_count`); \ No newline at end of file diff --git a/apps/web/drizzle/meta/0002_snapshot.json b/apps/web/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..9724f18 --- /dev/null +++ b/apps/web/drizzle/meta/0002_snapshot.json @@ -0,0 +1,347 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7a3efb1c-d2e3-4f56-a789-0b1234567890", + "prevId": "6f2de82b-c1f9-42de-b03c-1c1f0c02b7c9", + "tables": { + "diseases": { + "name": "diseases", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plant_id": { + "name": "plant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scientific_name": { + "name": "scientific_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "causal_agent_type": { + "name": "causal_agent_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "symptoms": { + "name": "symptoms", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "causes": { + "name": "causes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "treatment": { + "name": "treatment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "prevention": { + "name": "prevention", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "lookalike_ids": { + "name": "lookalike_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prevalence": { + "name": "prevalence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'uncommon'" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_diseases_plant_id": { + "name": "idx_diseases_plant_id", + "columns": ["plant_id"], + "isUnique": false + }, + "idx_diseases_causal_agent": { + "name": "idx_diseases_causal_agent", + "columns": ["causal_agent_type"], + "isUnique": false + }, + "idx_diseases_severity": { + "name": "idx_diseases_severity", + "columns": ["severity"], + "isUnique": false + }, + "idx_diseases_prevalence": { + "name": "idx_diseases_prevalence", + "columns": ["prevalence"], + "isUnique": false + } + }, + "foreignKeys": { + "diseases_plant_id_plants_id_fk": { + "name": "diseases_plant_id_plants_id_fk", + "tableFrom": "diseases", + "tableTo": "plants", + "columnsFrom": ["plant_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plants": { + "name": "plants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scientific_name": { + "name": "scientific_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "family": { + "name": "family", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "care_summary": { + "name": "care_summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_plants_category": { + "name": "idx_plants_category", + "columns": ["category"], + "isUnique": false + }, + "idx_plants_common_name": { + "name": "idx_plants_common_name", + "columns": ["common_name"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scrape_sources": { + "name": "scrape_sources", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_scraped_at": { + "name": "last_scraped_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entries_count": { + "name": "entries_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/web/drizzle/meta/0003_snapshot.json b/apps/web/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..9e49b2e --- /dev/null +++ b/apps/web/drizzle/meta/0003_snapshot.json @@ -0,0 +1,410 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "04ff83bd-e207-44d3-b8b7-8f82157bbeb8", + "prevId": "7a3efb1c-d2e3-4f56-a789-0b1234567890", + "tables": { + "diseases": { + "name": "diseases", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plant_id": { + "name": "plant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scientific_name": { + "name": "scientific_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "causal_agent_type": { + "name": "causal_agent_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "symptoms": { + "name": "symptoms", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "causes": { + "name": "causes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "treatment": { + "name": "treatment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "prevention": { + "name": "prevention", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "lookalike_ids": { + "name": "lookalike_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "prevalence": { + "name": "prevalence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'uncommon'" + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_diseases_plant_id": { + "name": "idx_diseases_plant_id", + "columns": [ + "plant_id" + ], + "isUnique": false + }, + "idx_diseases_causal_agent": { + "name": "idx_diseases_causal_agent", + "columns": [ + "causal_agent_type" + ], + "isUnique": false + }, + "idx_diseases_severity": { + "name": "idx_diseases_severity", + "columns": [ + "severity" + ], + "isUnique": false + }, + "idx_diseases_prevalence": { + "name": "idx_diseases_prevalence", + "columns": [ + "prevalence" + ], + "isUnique": false + } + }, + "foreignKeys": { + "diseases_plant_id_plants_id_fk": { + "name": "diseases_plant_id_plants_id_fk", + "tableFrom": "diseases", + "tableTo": "plants", + "columnsFrom": [ + "plant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plant_views": { + "name": "plant_views", + "columns": { + "plant_id": { + "name": "plant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "view_count": { + "name": "view_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_plant_views_count": { + "name": "idx_plant_views_count", + "columns": [ + "view_count" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plant_views_plant_id_plants_id_fk": { + "name": "plant_views_plant_id_plants_id_fk", + "tableFrom": "plant_views", + "tableTo": "plants", + "columnsFrom": [ + "plant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plants": { + "name": "plants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scientific_name": { + "name": "scientific_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "family": { + "name": "family", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "care_summary": { + "name": "care_summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_plants_category": { + "name": "idx_plants_category", + "columns": [ + "category" + ], + "isUnique": false + }, + "idx_plants_common_name": { + "name": "idx_plants_common_name", + "columns": [ + "common_name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scrape_sources": { + "name": "scrape_sources", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_scraped_at": { + "name": "last_scraped_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entries_count": { + "name": "entries_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/web/drizzle/meta/_journal.json b/apps/web/drizzle/meta/_journal.json index 62a3f78..144f90b 100644 --- a/apps/web/drizzle/meta/_journal.json +++ b/apps/web/drizzle/meta/_journal.json @@ -15,6 +15,20 @@ "when": 1780710023177, "tag": "0001_add-disease-images", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1749268800000, + "tag": "0002_add-prevalence", + "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1749268800000, + "tag": "0003_giant_toad", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index abd4fc8..801f599 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,6 +1,18 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + // Allow remote images from Wikimedia Commons + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "upload.wikimedia.org", + port: "", + pathname: "/wikipedia/commons/**", + search: "", + }, + ], + }, // Turbopack config (Next.js 16 default) turbopack: { resolveAlias: { diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index b665fe8..9b2381c 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -9,6 +9,9 @@ "version": "0.1.0", "dependencies": { "@libsql/client": "^0.17.3", + "@mudbill/duckduckgo-images-api": "^2.0.1", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-node": "^4.22.0", "dotenv": "^17.4.2", "drizzle-orm": "^0.45.2", "next": "16.2.7", @@ -2389,6 +2392,109 @@ "win32" ] }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", + "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mudbill/duckduckgo-images-api": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mudbill/duckduckgo-images-api/-/duckduckgo-images-api-2.0.1.tgz", + "integrity": "sha512-Mg6WCnwzfxQFtfx9gFCadzvEbkcBvDtfqXNj3qEJgqnI3GiGQl9lbm7YS8IBXqFHpFjo9U9SW2dcn++g5lPEog==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -3216,6 +3322,148 @@ "tailwindcss": "4.3.0" } }, + "node_modules/@tensorflow/tfjs": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-node": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.22.0.tgz", + "integrity": "sha512-uHrXeUlfgkMxTZqHkESSV7zSdKdV0LlsBeblqkuKU9nnfxB1pC6DtoyYVaLxznzZy7WQSegjcohxxCjAf6Dc7w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@mapbox/node-pre-gyp": "1.0.9", + "@tensorflow/tfjs": "4.22.0", + "adm-zip": "^0.5.2", + "google-protobuf": "^3.9.2", + "https-proxy-agent": "^2.2.1", + "progress": "^2.0.0", + "rimraf": "^2.6.2", + "tar": "^6.2.1" + }, + "engines": { + "node": ">=8.11.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -3391,6 +3639,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.41", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", @@ -3400,6 +3654,22 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.16", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", @@ -3420,6 +3690,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", + "license": "MIT" + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -4225,6 +4501,18 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -4248,6 +4536,27 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "license": "MIT", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -4269,9 +4578,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4280,7 +4587,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4292,6 +4598,26 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4515,6 +4841,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4555,7 +4887,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -4584,7 +4915,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4668,7 +4998,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4739,7 +5068,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -4752,17 +5080,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4775,16 +5122,41 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4792,6 +5164,17 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4914,7 +5297,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4978,6 +5360,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5660,7 +6057,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5785,7 +6181,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5795,7 +6190,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5840,7 +6234,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5853,7 +6246,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5896,6 +6288,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/esbuild": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", @@ -5942,7 +6349,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6528,6 +6934,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6547,7 +7005,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6584,6 +7041,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -6604,11 +7082,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6633,7 +7119,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6674,6 +7159,27 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -6717,11 +7223,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6754,7 +7265,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6793,7 +7303,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6806,7 +7315,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6818,11 +7326,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6868,6 +7381,28 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "license": "MIT", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6915,6 +7450,23 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -7114,6 +7666,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -7964,6 +8525,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8053,7 +8620,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8090,6 +8656,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8104,7 +8691,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -8123,11 +8709,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -8271,6 +8908,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.47", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", @@ -8281,11 +8960,38 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8418,6 +9124,15 @@ "node": ">=12.20.0" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8522,6 +9237,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8652,6 +9376,15 @@ "license": "MIT", "peer": true }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-limit": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", @@ -8729,6 +9462,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8766,6 +9513,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -8787,6 +9540,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8852,6 +9614,19 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/rolldown": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", @@ -8930,6 +9705,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -8984,6 +9779,12 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -8994,6 +9795,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9205,6 +10012,12 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9235,6 +10048,12 @@ "source-map": "^0.6.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -9270,6 +10089,35 @@ "node": ">= 0.4" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -9383,6 +10231,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -9446,7 +10306,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -9496,6 +10355,30 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -9937,6 +10820,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -10314,6 +11203,15 @@ "node": ">=8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -10324,6 +11222,29 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -10362,6 +11283,15 @@ "dev": true, "license": "MIT" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -10369,6 +11299,33 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/apps/web/package.json b/apps/web/package.json index a00a297..27df180 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,9 @@ }, "dependencies": { "@libsql/client": "^0.17.3", + "@mudbill/duckduckgo-images-api": "^2.0.1", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-node": "^4.22.0", "dotenv": "^17.4.2", "drizzle-orm": "^0.45.2", "next": "16.2.7", diff --git a/apps/web/public/models/plant-disease-classifier/best_mnv2_pv_original.keras b/apps/web/public/models/plant-disease-classifier/best_mnv2_pv_original.keras new file mode 100644 index 0000000..b0419c6 Binary files /dev/null and b/apps/web/public/models/plant-disease-classifier/best_mnv2_pv_original.keras differ diff --git a/apps/web/public/models/plant-disease-classifier/group1-shard1of3.bin b/apps/web/public/models/plant-disease-classifier/group1-shard1of3.bin new file mode 100644 index 0000000..0501dd5 Binary files /dev/null and b/apps/web/public/models/plant-disease-classifier/group1-shard1of3.bin differ diff --git a/apps/web/public/models/plant-disease-classifier/group1-shard2of3.bin b/apps/web/public/models/plant-disease-classifier/group1-shard2of3.bin new file mode 100644 index 0000000..5bc313d Binary files /dev/null and b/apps/web/public/models/plant-disease-classifier/group1-shard2of3.bin differ diff --git a/apps/web/public/models/plant-disease-classifier/group1-shard3of3.bin b/apps/web/public/models/plant-disease-classifier/group1-shard3of3.bin new file mode 100644 index 0000000..1f93489 Binary files /dev/null and b/apps/web/public/models/plant-disease-classifier/group1-shard3of3.bin differ diff --git a/apps/web/public/models/plant-disease-classifier/model.json b/apps/web/public/models/plant-disease-classifier/model.json new file mode 100644 index 0000000..c8f297e --- /dev/null +++ b/apps/web/public/models/plant-disease-classifier/model.json @@ -0,0 +1 @@ +{"format": "graph-model", "generatedBy": "2.21.0", "convertedBy": "TensorFlow.js Converter v3.18.0", "signature": {"inputs": {"input_layer_2": {"name": "input_layer_2:0", "dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "-1"}, {"size": "160"}, {"size": "160"}, {"size": "3"}]}}}, "outputs": {"output_0": {"name": "Identity:0", "dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "-1"}, {"size": "38"}]}}}}, "modelTopology": {"node": [{"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "24"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv1_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "3"}, {"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "32"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "32"}, {"size": "16"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "16"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "16"}, {"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_pad_1/Const", "op": "Const", "attr": {"dtype": {"type": "DT_INT32"}, "value": {"tensor": {"dtype": "DT_INT32", "tensorShape": {"dim": [{"size": "4"}, {"size": "2"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "96"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "96"}, {"size": "24"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "24"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "24"}, {"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "144"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "144"}, {"size": "24"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "24"}, {"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_pad_1/Const", "op": "Const", "attr": {"dtype": {"type": "DT_INT32"}, "value": {"tensor": {"dtype": "DT_INT32", "tensorShape": {"dim": [{"size": "4"}, {"size": "2"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "144"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "144"}, {"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "32"}, {"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "192"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "192"}, {"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "32"}, {"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "192"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "192"}, {"size": "32"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "32"}, {"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_pad_1/Const", "op": "Const", "attr": {"dtype": {"type": "DT_INT32"}, "value": {"tensor": {"dtype": "DT_INT32", "tensorShape": {"dim": [{"size": "4"}, {"size": "2"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "192"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "192"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "192"}, {"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "64"}, {"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "384"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "384"}, {"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "64"}, {"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "384"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "384"}, {"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "64"}, {"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "384"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "384"}, {"size": "64"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "64"}, {"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "384"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "384"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "384"}, {"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "96"}, {"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "576"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "576"}, {"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "96"}, {"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "576"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "576"}, {"size": "96"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "96"}, {"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_pad_1/Const", "op": "Const", "attr": {"dtype": {"type": "DT_INT32"}, "value": {"tensor": {"dtype": "DT_INT32", "tensorShape": {"dim": [{"size": "4"}, {"size": "2"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "576"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "576"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "576"}, {"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "160"}, {"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "960"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "960"}, {"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "160"}, {"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "960"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "960"}, {"size": "160"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "160"}, {"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_1/depthwise/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "960"}, {"size": "1"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/mul", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "960"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "960"}, {"size": "320"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "320"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_1/convolution/merged_input", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "320"}, {"size": "1280"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/sub", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1280"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/global_average_pooling2d_1/Mean/reduction_indices", "op": "Const", "attr": {"dtype": {"type": "DT_INT32"}, "value": {"tensor": {"dtype": "DT_INT32", "tensorShape": {"dim": [{"size": "2"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Cast/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1280"}, {"size": "38"}]}}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/BiasAdd/ReadVariableOp", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "38"}]}}}}}, {"name": "input_layer_2", "op": "Placeholder", "attr": {"dtype": {"type": "DT_FLOAT"}, "shape": {"shape": {"dim": [{"size": "-1"}, {"size": "160"}, {"size": "160"}, {"size": "3"}]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/mul_1", "op": "Conv2D", "input": ["input_layer_2", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv1_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "2", "2", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv1_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv1_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_pad_1/Pad", "op": "Pad", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_pad_1/Const"], "attr": {"T": {"type": "DT_FLOAT"}, "Tpaddings": {"type": "DT_INT32"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_pad_1/Pad", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "VkFMSUQ="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "2", "2", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_pad_1/Pad", "op": "Pad", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_pad_1/Const"], "attr": {"T": {"type": "DT_FLOAT"}, "Tpaddings": {"type": "DT_INT32"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_pad_1/Pad", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "VkFMSUQ="}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "2", "2", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}, "N": {"i": "2"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_pad_1/Pad", "op": "Pad", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_pad_1/Const"], "attr": {"T": {"type": "DT_FLOAT"}, "Tpaddings": {"type": "DT_INT32"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_pad_1/Pad", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "VkFMSUQ="}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "2", "2", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}, "N": {"i": "2"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_pad_1/Pad", "op": "Pad", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_pad_1/Const"], "attr": {"T": {"type": "DT_FLOAT"}, "Tpaddings": {"type": "DT_INT32"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_pad_1/Pad", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "VkFMSUQ="}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "2", "2", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "N": {"i": "2"}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "U0FNRQ=="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_1/depthwise/ReadVariableOp"], "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add", "op": "AddN", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_BN_1/batchnorm/mul_1"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}, "N": {"i": "2"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_BN_1/batchnorm/sub", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_add_1/ArithmeticOptimizer/AddOpsRewrite_Leaf_1_Add"], "attr": {"_grappler_ArithmeticOptimizer_AddOpsRewriteStage": {"b": true}, "T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_add_1/ArithmeticOptimizer/AddOpsRewrite_Add", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_1/depthwise", "op": "DepthwiseConv2dNative", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_1/depthwise/ReadVariableOp"], "attr": {"explicit_paddings": {"list": {}}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "data_format": {"s": "TkhXQw=="}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/mul_1", "op": "Mul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_1/depthwise", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/mul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "padding": {"s": "U0FNRQ=="}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/mul_1", "op": "Conv2D", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/add_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_1/convolution/merged_input"], "device": "/device:CPU:0", "attr": {"data_format": {"s": "TkhXQw=="}, "T": {"type": "DT_FLOAT"}, "dilations": {"list": {"i": ["1", "1", "1", "1"]}}, "padding": {"s": "VkFMSUQ="}, "use_cudnn_on_gpu": {"b": true}, "explicit_paddings": {"list": {}}, "strides": {"list": {"i": ["1", "1", "1", "1"]}}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/add_1", "op": "AddV2", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/mul_1", "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/sub"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/out_relu_1/Relu6", "op": "Relu6", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/add_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/global_average_pooling2d_1/Mean", "op": "Mean", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/out_relu_1/Relu6", "StatefulPartitionedCall/mnv2_pv_original_1/global_average_pooling2d_1/Mean/reduction_indices"], "attr": {"keep_dims": {"b": false}, "T": {"type": "DT_FLOAT"}, "Tidx": {"type": "DT_INT32"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/BiasAdd", "op": "_FusedMatMul", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/global_average_pooling2d_1/Mean", "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Cast/ReadVariableOp", "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/BiasAdd/ReadVariableOp"], "device": "/device:CPU:0", "attr": {"transpose_b": {"b": false}, "T": {"type": "DT_FLOAT"}, "epsilon": {"f": 0.0}, "fused_ops": {"list": {"s": ["Qmlhc0FkZA=="]}}, "leakyrelu_alpha": {"f": 0.2}, "transpose_a": {"b": false}, "num_args": {"i": "1"}}}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Softmax", "op": "Softmax", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/dense_1/BiasAdd"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "Identity", "op": "Identity", "input": ["StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Softmax"], "attr": {"T": {"type": "DT_FLOAT"}}}], "library": {}, "versions": {"producer": 2474}}, "weightsManifest": [{"paths": ["group1-shard1of3.bin", "group1-shard2of3.bin", "group1-shard3of3.bin"], "weights": [{"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_BN_1/batchnorm/sub", "shape": [160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_BN_1/batchnorm/sub", "shape": [160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_BN_1/batchnorm/sub", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_BN_1/batchnorm/sub", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_BN_1/batchnorm/sub", "shape": [64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_BN_1/batchnorm/sub", "shape": [64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_BN_1/batchnorm/sub", "shape": [64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_BN_1/batchnorm/sub", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_BN_1/batchnorm/sub", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_BN_1/batchnorm/sub", "shape": [24], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv1_1/convolution/merged_input", "shape": [3, 3, 3, 32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/bn_Conv1_1/batchnorm/sub", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 32, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/mul", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_depthwise_BN_1/batchnorm/sub", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_1/convolution/merged_input", "shape": [1, 1, 32, 16], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/expanded_conv_project_BN_1/batchnorm/sub", "shape": [16], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_1/convolution/merged_input", "shape": [1, 1, 16, 96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_expand_BN_1/batchnorm/sub", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_pad_1/Const", "shape": [4, 2], "dtype": "int32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 96, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/mul", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_depthwise_BN_1/batchnorm/sub", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_1/convolution/merged_input", "shape": [1, 1, 96, 24], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_1_project_BN_1/batchnorm/sub", "shape": [24], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_1/convolution/merged_input", "shape": [1, 1, 24, 144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_expand_BN_1/batchnorm/sub", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 144, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/mul", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_depthwise_BN_1/batchnorm/sub", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_2_project_1/convolution/merged_input", "shape": [1, 1, 144, 24], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_1/convolution/merged_input", "shape": [1, 1, 24, 144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_expand_BN_1/batchnorm/sub", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_pad_1/Const", "shape": [4, 2], "dtype": "int32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 144, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/mul", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_depthwise_BN_1/batchnorm/sub", "shape": [144], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_1/convolution/merged_input", "shape": [1, 1, 144, 32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_3_project_BN_1/batchnorm/sub", "shape": [32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_1/convolution/merged_input", "shape": [1, 1, 32, 192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_expand_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 192, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/mul", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_depthwise_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_4_project_1/convolution/merged_input", "shape": [1, 1, 192, 32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_1/convolution/merged_input", "shape": [1, 1, 32, 192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_expand_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 192, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/mul", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_depthwise_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_5_project_1/convolution/merged_input", "shape": [1, 1, 192, 32], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_1/convolution/merged_input", "shape": [1, 1, 32, 192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_expand_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_pad_1/Const", "shape": [4, 2], "dtype": "int32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 192, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/mul", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_depthwise_BN_1/batchnorm/sub", "shape": [192], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_1/convolution/merged_input", "shape": [1, 1, 192, 64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_6_project_BN_1/batchnorm/sub", "shape": [64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_1/convolution/merged_input", "shape": [1, 1, 64, 384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_expand_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 384, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/mul", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_depthwise_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_7_project_1/convolution/merged_input", "shape": [1, 1, 384, 64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_1/convolution/merged_input", "shape": [1, 1, 64, 384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_expand_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 384, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/mul", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_depthwise_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_8_project_1/convolution/merged_input", "shape": [1, 1, 384, 64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_1/convolution/merged_input", "shape": [1, 1, 64, 384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_expand_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 384, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/mul", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_depthwise_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_9_project_1/convolution/merged_input", "shape": [1, 1, 384, 64], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_1/convolution/merged_input", "shape": [1, 1, 64, 384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_expand_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 384, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/mul", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_depthwise_BN_1/batchnorm/sub", "shape": [384], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_1/convolution/merged_input", "shape": [1, 1, 384, 96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_10_project_BN_1/batchnorm/sub", "shape": [96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_1/convolution/merged_input", "shape": [1, 1, 96, 576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_expand_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 576, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/mul", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_depthwise_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_11_project_1/convolution/merged_input", "shape": [1, 1, 576, 96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_1/convolution/merged_input", "shape": [1, 1, 96, 576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_expand_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 576, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/mul", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_depthwise_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_12_project_1/convolution/merged_input", "shape": [1, 1, 576, 96], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_1/convolution/merged_input", "shape": [1, 1, 96, 576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_expand_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_pad_1/Const", "shape": [4, 2], "dtype": "int32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 576, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/mul", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_depthwise_BN_1/batchnorm/sub", "shape": [576], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_1/convolution/merged_input", "shape": [1, 1, 576, 160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_13_project_BN_1/batchnorm/sub", "shape": [160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_1/convolution/merged_input", "shape": [1, 1, 160, 960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_expand_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 960, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/mul", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_depthwise_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_14_project_1/convolution/merged_input", "shape": [1, 1, 960, 160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_1/convolution/merged_input", "shape": [1, 1, 160, 960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_expand_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 960, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/mul", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_depthwise_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_15_project_1/convolution/merged_input", "shape": [1, 1, 960, 160], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_1/convolution/merged_input", "shape": [1, 1, 160, 960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_expand_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_1/depthwise/ReadVariableOp", "shape": [3, 3, 960, 1], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/mul", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_depthwise_BN_1/batchnorm/sub", "shape": [960], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_1/convolution/merged_input", "shape": [1, 1, 960, 320], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/block_16_project_BN_1/batchnorm/sub", "shape": [320], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_1/convolution/merged_input", "shape": [1, 1, 320, 1280], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/mobilenetv2_1.00_160_1/Conv_1_bn_1/batchnorm/sub", "shape": [1280], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/global_average_pooling2d_1/Mean/reduction_indices", "shape": [2], "dtype": "int32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Cast/ReadVariableOp", "shape": [1280, 38], "dtype": "float32"}, {"name": "StatefulPartitionedCall/mnv2_pv_original_1/dense_1/BiasAdd/ReadVariableOp", "shape": [38], "dtype": "float32"}]}]} \ No newline at end of file diff --git a/apps/web/scripts/.brave-progress.json b/apps/web/scripts/.brave-progress.json new file mode 100644 index 0000000..eaeacf9 --- /dev/null +++ b/apps/web/scripts/.brave-progress.json @@ -0,0 +1,3657 @@ +{ + "processedIds": [ + "tomato-bacterial-canker-of-tomato", + "tomato-httpwwwgrincomene-book178581bacterial-speck-disease-of-tomato-an-insight-into-host-bacteria-interaction-bacterial-speck", + "tomato-bacterial-spot", + "tomato-bacterial-stem-rot-and-fruit-rot", + "tomato-bacterial-wilt", + "tomato-pith-necrosis", + "tomato-syringae-leaf-spot", + "tomato-aster-yellows", + "tomato-tomato-big-bud", + "tomato-", + "tomato-curly-top", + "tomato-potato-virus-y", + "tomato-pseudo-curly-top", + "tomato-tomato-bushy-stunt", + "tomato-tomato-etch", + "tomato-tomato-fern-leaf", + "tomato-tomato-mosaic", + "tomato-tomato-mottle", + "tomato-tomato-necrosis", + "tomato-tomato-spotted-wilt", + "tomato-tomato-yellow-leaf-curl", + "tomato-tomato-yellow-top", + "tomato-tomato-bunchy-top", + "tomato-tomato-planto-macho", + "potato-bacterial-wilt-brown-rot", + "potato-aligntopblackleg-and-bacterial-soft-rot", + "potato-pink-eye", + "potato-ring-rot", + "potato-zebra-chip-psyllid-yellows", + "potato-alfalfa-mosaic-virus", + "potato-andean-potato-latent-virus", + "potato-andean-potato-mottle-virus", + "potato-arracacha-virus-b-oca-strain", + "potato-beet-curly-top-virus", + "potato-cucumber-mosaic-virus", + "potato-eggplant-mottle-dwarf-virus", + "potato-potato-aucuba-mosaic-virus", + "potato-potato-black-ringspot-virus", + "potato-potato-deforming-mosaic-virus", + "potato-potato-latent-virus", + "potato-potato-leafroll-virus", + "potato-potato-mop-top-virus-spraing-of-tubers", + "potato-potato-rugose-mosaic", + "potato-potato-stem-mottle-spraing-of-tubers", + "potato-potato-spindle-tuber", + "potato-potato-yellow-dwarf-virus", + "potato-potato-yellow-mosaic-virus", + "potato-potato-yellow-vein-virus", + "potato-potato-yellowing-virus", + "potato-potato-virus-a", + "potato-potato-virus-m", + "potato-potato-virus-s", + "potato-potato-virus-h", + "potato-potato-virus-t", + "potato-potato-virus-u", + "potato-potato-virus-v", + "potato-potato-virus-x", + "potato-potato-virus-y", + "potato-solanum-apical-leaf-curling-virus", + "potato-sowbane-mosaic-virus", + "potato-tobacco-necrosis-virus", + "potato-tobacco-rattle-virus", + "potato-tobacco-streak-virus", + "potato-tomato-black-ring-virus", + "potato-tomato-mosaic-virus", + "potato-tomato-spotted-wilt-virus", + "potato-tomato-yellow-mosaic-virus", + "potato-wild-potato-mosaic-virus", + "potato-aster-yellows", + "potato-witches-broom", + "potato-bltva", + "apple-blister-spot", + "apple-hairy-root", + "apple-apple-chat-fruit", + "apple-apple-decline", + "apple-apple-proliferation", + "apple-rubbery-wood", + "apple-", + "apple-apple-chlorotic-leafspot", + "apple-apple-dwarf-malus-platycarpa", + "apple-apple-flat-apple", + "apple-apple-mosaic", + "apple-apple-stem-grooving-apple-decline-of-virginia-crab", + "apple-apple-stem-pitting-apple-spy-227-epinasty-and-decline", + "apple-apple-union-necrosis-and-decline", + "apple-swollen-apple", + "apple-apple-dimple-fruit", + "apple-apple-fruit-crinkle", + "apple-apple-scar-skin-apple-dapple-apple-sabi-ka-apple-bumpy-fruit", + "apple-dead-spur", + "apple-false-sting", + "apple-green-crinkle", + "apple-rough-skin", + "apple-star-crack", + "apricot-bacterial-canker-and-blast", + "apricot-bacterial-spot", + "apricot-", + "apricot-bare-twig-and-unfruitfulness", + "apricot-line-pattern-necrotic-ring-spot", + "apricot-peach-mosaic", + "apricot-plum-pox-sharka", + "apricot-prunus-stem-pitting", + "apricot-pseudopox", + "apricot-viral-gummosis", + "apricot-chlorotic-leaf-roll-apple-proliferation", + "avocado-bacterial-canker", + "avocado-blast-and-bacterial-fruit-spot", + "avocado-", + "barley-black-chaff-and-bacterial-streak", + "barley-bacterial-kernel-blight", + "barley-bacterial-leaf-blight", + "barley-bacterial-stripe", + "barley-basal-glume-rot", + "barley-bacterial-blight", + "barley-african-cereal-streak", + "barley-barley-mild-mosaic", + "barley-barley-mosaic", + "barley-barley-stripe-mosaic", + "barley-barley-yellow-dwarf", + "barley-barley-yellow-streak-mosaic", + "barley-barley-yellow-stripe", + "barley-brome-mosaic", + "barley-cereal-northern-mosaic-barley-yellow-striate-mosaic", + "barley-cereal-tillering", + "barley-chloris-striate-mosaic", + "barley-eastern-wheat-striate", + "barley-enanismo", + "barley-hordeum-mosaic", + "barley-oat-blue-dwarf", + "barley-oat-pseudorosette", + "barley-oat-sterile-dwarf", + "barley-rice-black-streaked-dwarf", + "barley-rice-stripe", + "barley-russian-winter-wheat-mosaic", + "barley-wheat-dwarf", + "barley-wheat-soil-borne-mosaic", + "barley-wheat-streak-mosaic", + "barley-wheat-yellow-leaf", + "barley-aster-yellows", + "carrot-bacterial-leaf-blight", + "carrot-carrot-bacterial-gall", + "carrot-carrot-bacteriosis", + "carrot-hairy-root", + "carrot-scab", + "carrot-", + "carrot-alfalfa-mosaic", + "carrot-carrot-latent", + "carrot-carrot-mottle", + "carrot-carrot-red-leaf", + "carrot-carrot-thin-leaf", + "carrot-carrot-yellow-leaf", + "carrot-celery-mosaic", + "carrot-cucumber-mosaic", + "carrot-curly-top", + "carrot-motley-dwarf", + "carrot-aster-yellows", + "citrus-bacterial-spot", + "citrus-black-pit-fruit", + "citrus-blast", + "citrus-citrus-variegated-chlorosis", + "citrus-huanglongbing-citrus-greening", + "citrus-", + "citrus-citrus-mosaic", + "citrus-bud-union-crease", + "citrus-citrus-leaf-rugose", + "citrus-citrus-yellow-mosaic", + "citrus-crinkly-leaf", + "citrus-infectious-variegation", + "citrus-navel-infectious-mottling", + "citrus-psorosis", + "citrus-satsuma-dwarf", + "citrus-tatter-leaf-citrange-stunt", + "citrus-tristeza-decline-and-stem-pitting-seedling-yellows", + "citrus-citrus-leprosis-virus-type-i-ii", + "citrus-algerian-navel-orange-virus", + "citrus-blight-young-tree-decline-rough-lemon-decline", + "citrus-blind-pocket", + "citrus-chlorotic-dwarf", + "citrus-citrus-dwarfing", + "citrus-citrus-vein-enation-cvev-woody-gall", + "citrus-citrus-yellow-mottle", + "citrus-citrus-yellow-ringspot", + "citrus-concave-gum", + "citrus-cristacortis", + "citrus-fatal-yellows", + "citrus-gummy-bark", + "citrus-gum-pocket-and-gummy-pittings", + "citrus-impietratura", + "citrus-indian-citrus-ringspot", + "citrus-leaf-curl", + "citrus-leathery-leaf", + "citrus-leprosis", + "citrus-milam-stem-pitting", + "citrus-multiple-sprouting-disease", + "citrus-nagami-kumquat-disease", + "citrus-ringspot-diseases", + "citrus-yellow-vein", + "citrus-yellow-vein-clearing-of-lemon", + "citrus-australian-citrus-dieback", + "coconut-", + "coconut-cadang-cadang", + "coconut-foliar-decay", + "coconut-tinangaja", + "coconut-natuna-wilt", + "coconut-premature-decline", + "coconut-soccoro-wilt", + "coconut-awka-diseasetexas-phoenix-palm-decline", + "coconut-blast", + "coconut-cape-st-paul-wilt", + "coconut-cedros-wilt", + "coconut-kaincope-disease", + "coconut-kalimantan-wilt", + "coconut-kribi-disease", + "coconut-lethal-decline", + "coconut-lethal-disease", + "coconut-pudricion-del-cogollo", + "coconut-root-wilt-disease", + "coconut-stem-necrosis", + "corn-bacterial-leaf-blight-and-stalk-rot", + "corn-bacterial-leaf-spot", + "corn-bacterial-leaf-streak-of-corn", + "corn-bacterial-stalk-rot", + "corn-bacterial-stalk-and-top-rot", + "corn-bacterial-stripe", + "corn-chocolate-spot", + "corn-gosss-bacterial-wilt-and-blight-leaf-freckles-and-wilt", + "corn-holcus-spot", + "corn-purple-leaf-sheath", + "corn-seed-rot-seedling-blight", + "corn-stewarts-disease-bacterial-wilt", + "corn-corn-stunt-achapparramiento-maize-stunt-mesa-central-or-rio-grande-maize-stunt", + "corn-", + "cucumber-bacterial-fruit-blotchseedling-blight", + "cucumber-bacterial-leaf-spot", + "cucumber-bacterial-rind-necrosis", + "cucumber-bacterial-wilt", + "cucumber-brown-spot", + "cucumber-", + "grape-happy-disease-bacterial-necrosis", + "grape-pierces-disease-pd", + "grape-bacterial-inflorescence-rot", + "grape-alfalfa-mosaic", + "grape-arabis-mosaic", + "grape-artichoke-italian-latent", + "grape-asteroid-mosaic", + "grape-bois-noirblack-wood-disease", + "grape-bratislava-mosaic", + "grape-broad-bean-wilt", + "grape-corky-bark", + "grape-enation", + "grape-fanleaf-degeneration-infectious-degeneration-and-decline", + "grape-flavescence-dore", + "grape-fleck-marbrure", + "grape-grapevine-bulgarian-latent", + "grape-grapevine-chrome-mosaic", + "grape-grapevine-red-blotch", + "grape-grapevine-yellows", + "grape-leafroll", + "grape-peach-rosette-mosaic-virus-decline", + "grape-petunia-asteroid-mosaic", + "grape-raspberry-ringspot", + "grape-rupestris-stem-pitting", + "grape-shoot-necrosis", + "grape-sowbane-mosaic", + "grape-strawberry-latent-ringspot", + "grape-tobacco-mosaic", + "grape-tobacco-necrosis", + "grape-tobacco-ringspot-virus-decline", + "grape-tomato-black-ring", + "grape-tomato-ringspot-virus-decline", + "grape-vein-mosaic", + "grape-yellow-speckle", + "lettuce-bacterial-leaf-spot-and-head-rot", + "lettuce-corky-root", + "lettuce-varnish-spot", + "lettuce-", + "lettuce-big-vein", + "lettuce-lettuce-dieback", + "lettuce-internal-rib-necrosis", + "lettuce-necrotic-yellows", + "lettuce-ring-necrosis", + "lettuce-rusty-brown-discoloration", + "lettuce-speckles", + "lettuce-wilt", + "lettuce-yellow-spot", + "lettuce-yellows", + "lettuce-other", + "mango-bacterial-black-spot-bacterial-canker", + "mango-bacterial-fruit-rot", + "mango-crown-gall-hi", + "papaya-bacterial-canker", + "papaya-bacterial-leaf-spot", + "papaya-bacterial-wilt", + "papaya-papaya-bunchy-top-disease", + "papaya-erwinia-decline", + "papaya-erwinia-mushy-canker", + "papaya-internal-yellowing", + "papaya-purple-stain", + "papaya-dieback", + "papaya-yellow-crinkle", + "papaya-apical-necrosis", + "papaya-droopy-necrosis", + "papaya-feather-leaf", + "papaya-leaf-curl", + "papaya-papaya-ringspot", + "papaya-papaya-lethal-yellowing", + "papaya-spotted-wilt", + "papaya-sticky-disease-meleira", + "papaya-terminal-necrosis-and-wilt", + "peanut-bacterial-wilt", + "peanut-", + "peanut-cowpea-mild-mottle", + "peanut-groundnut-crinkle", + "peanut-groundnut-eyespot", + "peanut-groundnut-rosette", + "peanut-groundnut-streak", + "peanut-marginal-chlorosis", + "peanut-peanut-clump", + "peanut-peanut-green-mosaic", + "peanut-peanut-mottle", + "peanut-peanut-ringspot-or-bud-necrosis", + "peanut-peanut-stripe", + "peanut-peanut-stunt", + "peanut-peanut-yellow-mottle", + "peanut-tomato-spotted-wilt", + "peanut-witches-broom", + "pear-pseudomonas-blossom-blast-and-canker", + "pear-pear-decline", + "pear-", + "pepper-bacterial-canker-of-tomato", + "pepper-httpwwwgrincomene-book178581bacterial-speck-disease-of-tomato-an-insight-into-host-bacteria-interaction-bacterial-speck", + "pepper-bacterial-spot", + "pepper-bacterial-stem-rot-and-fruit-rot", + "pepper-pith-necrosis", + "pepper-syringae-leaf-spot", + "pepper-aster-yellows", + "pepper-tomato-big-bud", + "pepper-", + "pepper-curly-top", + "pepper-potato-virus-y", + "pepper-pseudo-curly-top", + "pepper-tomato-bushy-stunt", + "pepper-tomato-etch", + "pepper-tomato-fern-leaf", + "pepper-tomato-mosaic", + "pepper-tomato-mottle", + "pepper-tomato-necrosis", + "pepper-tomato-spotted-wilt", + "pepper-tomato-yellow-leaf-curl", + "pepper-tomato-yellow-top", + "pepper-tomato-bunchy-top", + "pepper-tomato-planto-macho", + "pineapple-bacterial-heart-rot", + "pineapple-", + "pineapple-acetic-souring", + "pineapple-bacterial-fruitlet-brown-rot", + "pineapple-fruit-collapse", + "pineapple-marbled-fruit", + "pineapple-pink-fruit", + "pineapple-soft-rot", + "rice-bacterial-blight", + "rice-bacterial-leaf-streak", + "rice-foot-rot", + "rice-grain-rot", + "rice-pecky-rice-kernel-spotting", + "rice-sheath-brown-rot", + "rice-", + "sorghum-bacterial-leaf-spot", + "sorghum-bacterial-leaf-streak", + "sorghum-bacterial-leaf-stripe", + "sorghum-", + "sorghum-yellow-sorghum-stunt", + "soybean-bacterial-blight", + "soybean-bacterial-tan-spot", + "soybean-bacterial-wilt", + "soybean-", + "soybean-alfalfa-mosaic", + "soybean-bean-pod-mottle", + "soybean-bean-yellow-mosaic", + "soybean-brazilian-bud-blight", + "soybean-cowpea-chlorotic-mottle", + "soybean-mung-bean-yellow-mosaic", + "soybean-peanut-mottle", + "soybean-peanut-stripe", + "soybean-peanut-stunt", + "soybean-soybean-chlorotic-mottle", + "soybean-soybean-dwarf", + "soybean-soybean-mosaic", + "soybean-soybean-severe-stunt", + "soybean-tobacco-ringspot-bud-blight", + "spinach-bacterial-leaf-spot", + "spinach-witches-broom", + "spinach-", + "spinach-curly-top", + "spinach-speckles", + "spinach-spinach-blight", + "spinach-spinach-mosaic", + "spinach-yellow-dwarf", + "spinach-yellows", + "spinach-aster-yellows", + "strawberry-angular-leaf-spot", + "strawberry-bacterial-wilt", + "strawberry-cauliflower-disease-complex", + "strawberry-", + "strawberry-strawberry-chlorotic-fleck", + "strawberry-strawberry-crinkle", + "strawberry-strawberry-latent-c-virus-in-fragaria", + "strawberry-strawberry-mild-yellow-edge", + "strawberry-strawberry-mottle", + "strawberry-strawberry-pseudo-mild-yellow-edge", + "strawberry-strawberry-vein-banding", + "strawberry-aster-yellows", + "strawberry-maladie-du-bord-jaune", + "strawberry-strawberry-green-petal", + "strawberry-strawberry-lethal-decline", + "strawberry-strawberry-multiplier-plant", + "strawberry-strawberry-mycoplasma-yellows-disease", + "strawberry-strawberry-rickettsia-yellows-disease", + "strawberry-strawberry-witches-broom", + "strawberry-arabis-mosaic-virus", + "strawberry-raspberry-ringspot-virus", + "strawberry-strawberry-latent-ringspot-virus", + "strawberry-tomato-black-ring-virus", + "strawberry-tomato-ringspot-virus", + "strawberry-tobacco-necrosis-virus-in-fragaria-vesca", + "strawberry-strawberry-pallidosis", + "strawberry-strawberry-necrotic-shock", + "strawberry-strawberry-leafroll", + "strawberry-strawberry-feather-leaf", + "strawberry-strawberry-june-yellows", + "sugarcane-gumming-disease", + "sugarcane-leaf-scald", + "sugarcane-mottled-stripe", + "sugarcane-ratoon-stunting-disease", + "sugarcane-red-stripe-top-rot", + "sugarcane-", + "sugarcane-dwarf", + "sugarcane-fiji-disease", + "sugarcane-sereh", + "sugarcane-streak-disease", + "sugarcane-yellow-leaf", + "sugarcane-grassy-shoot-scgs-leaf-chlorosis-early-bud-sprouting", + "sunflower-apical-chlorosis", + "sunflower-bacterial-leaf-spot", + "sunflower-bacterial-wilt", + "sunflower-erwinia-stalk-rot-and-head-rot", + "sunflower-", + "sunflower-aster-yellows", + "sunflower-sunflower-mosaic", + "sunflower-sunflower-virus", + "sunflower-chlorotic-mottle", + "sweet-potato-bacterial-leaf-spot", + "sweet-potato-bacterial-stem-and-root-rot", + "sweet-potato-bacterial-wilt", + "sweet-potato-hairy-roots", + "sweet-potato-little-leaf-proliferation-disease", + "sweet-potato-feathery-mottle", + "sweet-potato-internal-cork", + "sweet-potato-latent-virus", + "sweet-potato-mild-mottle", + "sweet-potato-russet-crack", + "sweet-potato-sweet-potato-virus-disease-spvd", + "tobacco-angular-leaf-spot-synonym-wildfire-black-fire", + "tobacco-granville-wilt", + "tobacco-hairy-roots", + "tobacco-hollow-stalk", + "tobacco-leaf-gall", + "tobacco-", + "tobacco-alfalfa-mosaic", + "tobacco-aster-yellows", + "tobacco-beet-curly-top", + "tobacco-bushy-top", + "tobacco-cucumber-mosaic", + "tobacco-lettuce-necrotic-yellows", + "tobacco-peanut-stunt", + "tobacco-rosette-disease", + "tobacco-stolbur", + "tobacco-tobacco-etch", + "tobacco-tobacco-leaf-curl", + "tobacco-tobacco-mosaic", + "tobacco-tobacco-necrosis", + "tobacco-tobacco-rattle", + "tobacco-tobacco-ring-spot", + "tobacco-tobacco-streak", + "tobacco-tobacco-stunt", + "tobacco-tobacco-vein-mottling", + "tobacco-tomato-spotted-wilt", + "tobacco-vein-banding", + "tobacco-wound-tumor", + "watermelon-angular-leaf-spot", + "watermelon-bacterial-fruit-blotchseedling-blight", + "watermelon-bacterial-leaf-spot", + "watermelon-bacterial-rind-necrosis", + "watermelon-bacterial-wilt", + "watermelon-brown-spot", + "watermelon-", + "wheat-bacterial-leaf-blight", + "wheat-bacterial-mosaic", + "wheat-bacterial-sheath-rot", + "wheat-basal-glume-rot", + "wheat-black-chaff-bacterial-leaf-streak", + "wheat-pink-seed", + "wheat-spike-blight-gummosis", + "wheat-", + "wheat-african-cereal-streak", + "wheat-agropyron-mosaic", + "wheat-australian-wheat-striate-mosaic", + "wheat-barley-stripe-mosaic", + "wheat-barley-yellow-dwarf", + "wheat-barley-yellow-streak-mosaic", + "wheat-barley-yellow-striate-mosaic", + "wheat-barley-yellow-stripe", + "wheat-brome-mosaic", + "wheat-cereal-northern-mosaic", + "wheat-cereal-tillering", + "wheat-chloris-striate-mosaic", + "wheat-cocksfoot-mottle", + "wheat-eastern-wheat-striate", + "wheat-enanismo", + "wheat-high-plains-disease", + "wheat-maize-streak", + "wheat-northern-cereal-mosaic", + "wheat-oat-sterile-dwarf", + "wheat-rice-black-streaked-dwarf", + "wheat-rice-hoja-blanca", + "wheat-russian-winter-wheat-mosaic", + "wheat-seedborne-wheat-yellows", + "wheat-tobacco-mosaic", + "wheat-wheat-american-striate-mosaic", + "wheat-wheat-chlorotic-streak-wheat-chlorotic-streak-mosaic", + "wheat-wheat-dwarf", + "wheat-wheat-european-striate-mosaic", + "wheat-wheat-rosette-stunt", + "wheat-wheat-soilborne-mosaic", + "wheat-wheat-soilborne-yellow-mosaic", + "wheat-wheat-spindle-streak-mosaic", + "wheat-wheat-spot-mosaic", + "wheat-wheat-streak-mosaic", + "wheat-wheat-striate-mosaic", + "wheat-wheat-yellow-leaf", + "wheat-wheat-yellow-mosaic", + "wheat-aster-yellows", + "alfalfa-bacterial-leaf-spot", + "alfalfa-bacterial-sprout-rot", + "alfalfa-bacterial-stem-blight", + "alfalfa-bacterial-wilt", + "alfalfa-crown-and-root-rot-complex", + "alfalfa-dwarf", + "alfalfa-", + "alfalfa-alfalfa-enation", + "alfalfa-alfalfa-mosaic", + "alfalfa-bean-leaf-roll", + "alfalfa-bean-yellow-mosaic", + "alfalfa-cucumber-mosaic", + "alfalfa-lucerne-australian-latent", + "alfalfa-lucerne-australian-symptomless", + "alfalfa-lucerne-transient-streak", + "alfalfa-pea-streak", + "alfalfa-red-clover-vein-mosaic", + "alfalfa-tobacco-streak", + "alfalfa-white-clover-mosaic", + "alfalfa-aster-yellows", + "alfalfa-witches-broom", + "asparagus-asparagus-decline", + "asparagus-asparagus-mosaic", + "tea-bacterial-canker", + "tea-bacterial-shoot-blight", + "tea-", + "tomato-mosaic-virus", + "tomato-root-knot-nematode", + "tomato-canker-stembranch", + "potato-mosaic-virus", + "potato-canker-stembranch", + "bell-pepper-mosaic-virus", + "bell-pepper-root-knot-nematode", + "bell-pepper-canker-stembranch", + "chili-pepper-mosaic-virus", + "chili-pepper-root-knot-nematode", + "chili-pepper-canker-stembranch", + "eggplant-mosaic-virus", + "eggplant-root-knot-nematode", + "eggplant-canker-stembranch", + "tobacco-mosaic-virus", + "tobacco-root-knot-nematode", + "tobacco-canker-stembranch", + "tomatillo-mosaic-virus", + "tomatillo-root-knot-nematode", + "tomatillo-canker-stembranch", + "petunia-mosaic-virus", + "petunia-root-knot-nematode", + "petunia-canker-stembranch", + "gooseberry-mosaic-virus", + "gooseberry-root-knot-nematode", + "gooseberry-canker-stembranch", + "cucumber-mosaic-virus", + "cucumber-root-knot-nematode", + "cucumber-canker-stembranch", + "zucchini-root-knot-nematode", + "zucchini-canker-stembranch", + "summer-squash-mosaic-virus", + "summer-squash-root-knot-nematode", + "summer-squash-canker-stembranch", + "winter-squash-mosaic-virus", + "winter-squash-root-knot-nematode", + "winter-squash-canker-stembranch", + "pumpkin-mosaic-virus", + "pumpkin-root-knot-nematode", + "pumpkin-canker-stembranch", + "watermelon-mosaic-virus", + "watermelon-root-knot-nematode", + "watermelon-canker-stembranch", + "cantaloupe-mosaic-virus", + "cantaloupe-root-knot-nematode", + "cantaloupe-canker-stembranch", + "honeydew-mosaic-virus", + "honeydew-root-knot-nematode", + "honeydew-canker-stembranch", + "bitter-melon-mosaic-virus", + "bitter-melon-root-knot-nematode", + "bitter-melon-canker-stembranch", + "chayote-mosaic-virus", + "chayote-root-knot-nematode", + "chayote-canker-stembranch", + "acorn-squash-mosaic-virus", + "acorn-squash-root-knot-nematode", + "acorn-squash-canker-stembranch", + "butternut-squash-mosaic-virus", + "butternut-squash-root-knot-nematode", + "butternut-squash-canker-stembranch", + "calabash-mosaic-virus", + "calabash-root-knot-nematode", + "calabash-canker-stembranch", + "luffa-mosaic-virus", + "luffa-root-knot-nematode", + "luffa-canker-stembranch", + "apple-mosaic-virus", + "apple-canker-stembranch", + "pear-mosaic-virus", + "pear-root-knot-nematode", + "pear-canker-stembranch", + "peach-mosaic-virus", + "peach-root-knot-nematode", + "peach-canker-stembranch", + "cherry-mosaic-virus", + "cherry-root-knot-nematode", + "cherry-canker-stembranch", + "apricot-mosaic-virus", + "apricot-root-knot-nematode", + "apricot-canker-stembranch", + "plum-mosaic-virus", + "plum-root-knot-nematode", + "plum-canker-stembranch", + "almond-mosaic-virus", + "almond-root-knot-nematode", + "almond-canker-stembranch", + "strawberry-mosaic-virus", + "strawberry-root-knot-nematode", + "strawberry-canker-stembranch", + "raspberry-mosaic-virus", + "raspberry-root-knot-nematode", + "raspberry-canker-stembranch", + "blackberry-mosaic-virus", + "blackberry-root-knot-nematode", + "blackberry-canker-stembranch", + "blueberry-mosaic-virus", + "blueberry-root-knot-nematode", + "blueberry-canker-stembranch", + "cranberry-mosaic-virus", + "cranberry-root-knot-nematode", + "cranberry-canker-stembranch", + "rose-mosaic-virus", + "rose-root-knot-nematode", + "rose-canker-stembranch", + "hawthorn-mosaic-virus", + "hawthorn-root-knot-nematode", + "hawthorn-canker-stembranch", + "quince-mosaic-virus", + "quince-root-knot-nematode", + "quince-canker-stembranch", + "cabbage-mosaic-virus", + "cabbage-root-knot-nematode", + "cabbage-canker-stembranch", + "broccoli-mosaic-virus", + "broccoli-root-knot-nematode", + "broccoli-canker-stembranch", + "cauliflower-mosaic-virus", + "cauliflower-root-knot-nematode", + "cauliflower-canker-stembranch", + "brussels-sprouts-mosaic-virus", + "brussels-sprouts-root-knot-nematode", + "brussels-sprouts-canker-stembranch", + "kale-mosaic-virus", + "kale-root-knot-nematode", + "kale-canker-stembranch", + "bok-choy-mosaic-virus", + "bok-choy-root-knot-nematode", + "bok-choy-canker-stembranch", + "radish-mosaic-virus", + "radish-root-knot-nematode", + "radish-canker-stembranch", + "turnip-mosaic-virus", + "turnip-root-knot-nematode", + "turnip-canker-stembranch", + "arugula-mosaic-virus", + "arugula-root-knot-nematode", + "arugula-canker-stembranch", + "collard-greens-mosaic-virus", + "collard-greens-root-knot-nematode", + "collard-greens-canker-stembranch", + "mustard-greens-mosaic-virus", + "mustard-greens-root-knot-nematode", + "mustard-greens-canker-stembranch", + "horseradish-mosaic-virus", + "horseradish-root-knot-nematode", + "horseradish-canker-stembranch", + "wasabi-mosaic-virus", + "wasabi-root-knot-nematode", + "wasabi-canker-stembranch", + "green-bean-mosaic-virus", + "green-bean-root-knot-nematode", + "green-bean-canker-stembranch", + "soybean-mosaic-virus", + "soybean-canker-stembranch", + "peanut-mosaic-virus", + "peanut-root-knot-nematode", + "peanut-canker-stembranch", + "chickpea-mosaic-virus", + "chickpea-root-knot-nematode", + "chickpea-canker-stembranch", + "lentil-mosaic-virus", + "lentil-root-knot-nematode", + "lentil-canker-stembranch", + "faba-bean-mosaic-virus", + "faba-bean-root-knot-nematode", + "faba-bean-canker-stembranch", + "cowpea-mosaic-virus", + "cowpea-root-knot-nematode", + "cowpea-canker-stembranch", + "pigeon-pea-mosaic-virus", + "pigeon-pea-root-knot-nematode", + "pigeon-pea-canker-stembranch", + "alfalfa-mosaic-virus", + "alfalfa-canker-stembranch", + "clover-mosaic-virus", + "clover-root-knot-nematode", + "clover-canker-stembranch", + "peas-mosaic-virus", + "peas-root-knot-nematode", + "peas-canker-stembranch", + "lupine-mosaic-virus", + "lupine-root-knot-nematode", + "lupine-canker-stembranch", + "wisteria-mosaic-virus", + "wisteria-root-knot-nematode", + "wisteria-canker-stembranch", + "robinia-mosaic-virus", + "robinia-root-knot-nematode", + "robinia-canker-stembranch", + "corn-mosaic-virus", + "corn-root-knot-nematode", + "corn-canker-stembranch", + "wheat-mosaic-virus", + "wheat-root-knot-nematode", + "wheat-canker-stembranch", + "rice-mosaic-virus", + "rice-root-knot-nematode", + "rice-canker-stembranch", + "barley-mosaic-virus", + "barley-root-knot-nematode", + "barley-canker-stembranch", + "oats-mosaic-virus", + "oats-root-knot-nematode", + "oats-canker-stembranch", + "sorghum-mosaic-virus", + "sorghum-root-knot-nematode", + "sorghum-canker-stembranch", + "sugarcane-mosaic-virus", + "sugarcane-root-knot-nematode", + "sugarcane-canker-stembranch", + "bamboo-mosaic-virus", + "bamboo-root-knot-nematode", + "bamboo-canker-stembranch", + "turfgrass-mosaic-virus", + "turfgrass-root-knot-nematode", + "turfgrass-canker-stembranch", + "millet-mosaic-virus", + "millet-root-knot-nematode", + "millet-canker-stembranch", + "rye-mosaic-virus", + "rye-root-knot-nematode", + "rye-canker-stembranch", + "sunflower-mosaic-virus", + "sunflower-root-knot-nematode", + "sunflower-canker-stembranch", + "lettuce-mosaic-virus", + "lettuce-root-knot-nematode", + "lettuce-canker-stembranch", + "artichoke-mosaic-virus", + "artichoke-root-knot-nematode", + "artichoke-canker-stembranch", + "chicory-mosaic-virus", + "chicory-root-knot-nematode", + "chicory-canker-stembranch", + "endive-mosaic-virus", + "endive-root-knot-nematode", + "endive-canker-stembranch", + "daisy-mosaic-virus", + "daisy-root-knot-nematode", + "daisy-canker-stembranch", + "marigold-mosaic-virus", + "marigold-root-knot-nematode", + "marigold-canker-stembranch", + "zinnia-mosaic-virus", + "zinnia-root-knot-nematode", + "zinnia-canker-stembranch", + "chrysanthemum-mosaic-virus", + "chrysanthemum-root-knot-nematode", + "chrysanthemum-canker-stembranch", + "dahlia-mosaic-virus", + "dahlia-root-knot-nematode", + "dahlia-canker-stembranch", + "calendula-mosaic-virus", + "calendula-root-knot-nematode", + "calendula-canker-stembranch", + "echinacea-mosaic-virus", + "echinacea-root-knot-nematode", + "echinacea-canker-stembranch", + "yarrow-mosaic-virus", + "yarrow-root-knot-nematode", + "yarrow-canker-stembranch", + "tarragon-mosaic-virus", + "tarragon-root-knot-nematode", + "tarragon-canker-stembranch", + "stevia-mosaic-virus", + "stevia-root-knot-nematode", + "stevia-canker-stembranch", + "basil-mosaic-virus", + "basil-root-knot-nematode", + "basil-canker-stembranch", + "mint-mosaic-virus", + "mint-root-knot-nematode", + "mint-canker-stembranch", + "lavender-mosaic-virus", + "lavender-root-knot-nematode", + "lavender-canker-stembranch", + "rosemary-mosaic-virus", + "rosemary-root-knot-nematode", + "rosemary-canker-stembranch", + "thyme-mosaic-virus", + "thyme-root-knot-nematode", + "thyme-canker-stembranch", + "oregano-mosaic-virus", + "oregano-root-knot-nematode", + "oregano-canker-stembranch", + "sage-mosaic-virus", + "sage-root-knot-nematode", + "sage-canker-stembranch", + "lemon-balm-mosaic-virus", + "lemon-balm-root-knot-nematode", + "lemon-balm-canker-stembranch", + "catnip-mosaic-virus", + "catnip-root-knot-nematode", + "catnip-canker-stembranch", + "coleus-mosaic-virus", + "coleus-root-knot-nematode", + "coleus-canker-stembranch", + "carrot-mosaic-virus", + "carrot-root-knot-nematode", + "carrot-canker-stembranch", + "celery-mosaic-virus", + "celery-root-knot-nematode", + "celery-canker-stembranch", + "parsley-mosaic-virus", + "parsley-root-knot-nematode", + "parsley-canker-stembranch", + "cilantro-mosaic-virus", + "cilantro-root-knot-nematode", + "cilantro-canker-stembranch", + "dill-mosaic-virus", + "dill-root-knot-nematode", + "dill-canker-stembranch", + "fennel-mosaic-virus", + "fennel-root-knot-nematode", + "fennel-canker-stembranch", + "parsnip-mosaic-virus", + "parsnip-root-knot-nematode", + "parsnip-canker-stembranch", + "cumin-mosaic-virus", + "cumin-root-knot-nematode", + "cumin-canker-stembranch", + "onion-mosaic-virus", + "onion-root-knot-nematode", + "onion-canker-stembranch", + "garlic-mosaic-virus", + "garlic-root-knot-nematode", + "garlic-canker-stembranch", + "leek-mosaic-virus", + "leek-root-knot-nematode", + "leek-canker-stembranch", + "shallot-damping-off", + "shallot-gray-mold-botrytis-blight", + "shallot-mosaic-virus", + "shallot-wilt-fusarium-or-verticillium", + "shallot-root-knot-nematode", + "shallot-canker-stembranch", + "chive-root-rot-pythiumphytophthora", + "chive-damping-off", + "chive-gray-mold-botrytis-blight", + "chive-mosaic-virus", + "chive-wilt-fusarium-or-verticillium", + "chive-root-knot-nematode", + "chive-canker-stembranch", + "monstera-root-rot-aroidsoverwatering", + "monstera-root-rot-pythiumphytophthora", + "monstera-damping-off", + "monstera-gray-mold-botrytis-blight", + "monstera-mosaic-virus", + "monstera-wilt-fusarium-or-verticillium", + "monstera-root-knot-nematode", + "monstera-canker-stembranch", + "pothos-mosaic-virus", + "pothos-root-knot-nematode", + "pothos-canker-stembranch", + "peace-lily-mosaic-virus", + "peace-lily-root-knot-nematode", + "peace-lily-canker-stembranch", + "philodendron-mosaic-virus", + "philodendron-root-knot-nematode", + "philodendron-canker-stembranch", + "anthurium-mosaic-virus", + "anthurium-root-knot-nematode", + "anthurium-canker-stembranch", + "alocasia-mosaic-virus", + "alocasia-root-knot-nematode", + "alocasia-canker-stembranch", + "caladium-mosaic-virus", + "caladium-root-knot-nematode", + "caladium-canker-stembranch", + "aglaonema-mosaic-virus", + "aglaonema-root-knot-nematode", + "aglaonema-canker-stembranch", + "dieffenbachia-mosaic-virus", + "dieffenbachia-root-knot-nematode", + "dieffenbachia-canker-stembranch", + "spathiphyllum-mosaic-virus", + "spathiphyllum-root-knot-nematode", + "spathiphyllum-canker-stembranch", + "asparagus-mosaic-virus", + "asparagus-root-knot-nematode", + "asparagus-canker-stembranch", + "snake-plant-mosaic-virus", + "snake-plant-root-knot-nematode", + "snake-plant-canker-stembranch", + "yucca-mosaic-virus", + "yucca-root-knot-nematode", + "yucca-canker-stembranch", + "dracaena-mosaic-virus", + "dracaena-root-knot-nematode", + "dracaena-canker-stembranch", + "lily-of-the-valley-mosaic-virus", + "lily-of-the-valley-root-knot-nematode", + "lily-of-the-valley-canker-stembranch", + "hosta-mosaic-virus", + "hosta-root-knot-nematode", + "hosta-canker-stembranch", + "orchid-phalaenopsis-mosaic-virus", + "orchid-phalaenopsis-root-knot-nematode", + "orchid-phalaenopsis-canker-stembranch", + "orchid-cattleya-mosaic-virus", + "orchid-cattleya-root-knot-nematode", + "orchid-cattleya-canker-stembranch", + "orchid-dendrobium-mosaic-virus", + "orchid-dendrobium-root-knot-nematode", + "orchid-dendrobium-canker-stembranch", + "orchid-oncidium-mosaic-virus", + "orchid-oncidium-root-knot-nematode", + "orchid-oncidium-canker-stembranch", + "vanilla-mosaic-virus", + "vanilla-root-knot-nematode", + "vanilla-canker-stembranch", + "prickly-pear-mosaic-virus", + "prickly-pear-root-knot-nematode", + "prickly-pear-canker-stembranch", + "barrel-cactus-mosaic-virus", + "barrel-cactus-root-knot-nematode", + "barrel-cactus-canker-stembranch", + "christmas-cactus-mosaic-virus", + "christmas-cactus-root-knot-nematode", + "christmas-cactus-canker-stembranch", + "saguaro-mosaic-virus", + "saguaro-root-knot-nematode", + "saguaro-canker-stembranch", + "aloe-vera-mosaic-virus", + "aloe-vera-root-knot-nematode", + "aloe-vera-canker-stembranch", + "agave-mosaic-virus", + "agave-root-knot-nematode", + "agave-canker-stembranch", + "echeveria-mosaic-virus", + "echeveria-root-knot-nematode", + "echeveria-canker-stembranch", + "jade-plant-mosaic-virus", + "jade-plant-root-knot-nematode", + "jade-plant-canker-stembranch", + "sedum-mosaic-virus", + "sedum-root-knot-nematode", + "sedum-canker-stembranch", + "haworthia-mosaic-virus", + "haworthia-root-knot-nematode", + "haworthia-canker-stembranch", + "poinsettia-mosaic-virus", + "poinsettia-root-knot-nematode", + "poinsettia-canker-stembranch", + "cassava-mosaic-virus", + "cassava-root-knot-nematode", + "cassava-canker-stembranch", + "castor-bean-mosaic-virus", + "castor-bean-root-knot-nematode", + "castor-bean-canker-stembranch", + "crown-of-thorns-mosaic-virus", + "crown-of-thorns-root-knot-nematode", + "crown-of-thorns-canker-stembranch", + "orange-mosaic-virus", + "orange-root-knot-nematode", + "orange-canker-stembranch", + "lemon-mosaic-virus", + "lemon-root-knot-nematode", + "lemon-canker-stembranch", + "lime-mosaic-virus", + "lime-root-knot-nematode", + "lime-canker-stembranch", + "grapefruit-mosaic-virus", + "grapefruit-root-knot-nematode", + "grapefruit-canker-stembranch", + "mandarin-mosaic-virus", + "mandarin-root-knot-nematode", + "mandarin-canker-stembranch", + "kumquat-mosaic-virus", + "kumquat-root-knot-nematode", + "kumquat-canker-stembranch", + "grape-mosaic-virus", + "grape-root-knot-nematode", + "grape-canker-stembranch", + "muscadine-mosaic-virus", + "muscadine-root-knot-nematode", + "muscadine-canker-stembranch", + "banana-mosaic-virus", + "banana-root-knot-nematode", + "banana-canker-stembranch", + "plantain-mosaic-virus", + "bird-of-paradise-root-knot-nematode", + "bird-of-paradise-canker-stembranch", + "avocado-mosaic-virus", + "avocado-root-knot-nematode", + "avocado-canker-stembranch", + "cinnamon-mosaic-virus", + "cinnamon-root-knot-nematode", + "cinnamon-canker-stembranch", + "bay-laurel-mosaic-virus", + "bay-laurel-root-knot-nematode", + "bay-laurel-canker-stembranch", + "cocoa-mosaic-virus", + "cocoa-root-knot-nematode", + "cocoa-canker-stembranch", + "cotton-mosaic-virus", + "cotton-root-knot-nematode", + "cotton-canker-stembranch", + "okra-mosaic-virus", + "okra-root-knot-nematode", + "okra-canker-stembranch", + "hibiscus-mosaic-virus", + "hibiscus-root-knot-nematode", + "hibiscus-canker-stembranch", + "hollyhock-mosaic-virus", + "hollyhock-wilt-fusarium-or-verticillium", + "hollyhock-root-knot-nematode", + "hollyhock-canker-stembranch", + "baobab-root-rot-pythiumphytophthora", + "baobab-damping-off", + "baobab-gray-mold-botrytis-blight", + "baobab-mosaic-virus", + "baobab-wilt-fusarium-or-verticillium", + "baobab-root-knot-nematode", + "baobab-canker-stembranch", + "durian-root-rot-pythiumphytophthora", + "durian-damping-off", + "durian-gray-mold-botrytis-blight", + "durian-mosaic-virus", + "durian-wilt-fusarium-or-verticillium", + "durian-root-knot-nematode", + "durian-canker-stembranch", + "coconut-root-rot-pythiumphytophthora", + "coconut-gray-mold-botrytis-blight", + "coconut-mosaic-virus", + "coconut-root-knot-nematode", + "coconut-canker-stembranch", + "oil-palm-mosaic-virus", + "oil-palm-root-knot-nematode", + "oil-palm-canker-stembranch", + "date-palm-mosaic-virus", + "date-palm-root-knot-nematode", + "date-palm-canker-stembranch", + "palm-areca-mosaic-virus", + "palm-areca-root-knot-nematode", + "palm-areca-canker-stembranch", + "palm-parlor-mosaic-virus", + "palm-parlor-root-knot-nematode", + "palm-parlor-canker-stembranch", + "palm-kentia-mosaic-virus", + "palm-kentia-root-knot-nematode", + "palm-kentia-canker-stembranch", + "mango-mosaic-virus", + "mango-root-knot-nematode", + "mango-canker-stembranch", + "cashew-mosaic-virus", + "cashew-root-knot-nematode", + "cashew-canker-stembranch", + "pistachio-mosaic-virus", + "pistachio-root-knot-nematode", + "pistachio-canker-stembranch", + "poison-ivy-mosaic-virus", + "poison-ivy-root-knot-nematode", + "poison-ivy-canker-stembranch", + "coffee-mosaic-virus", + "coffee-root-knot-nematode", + "coffee-canker-stembranch", + "gardenia-mosaic-virus", + "gardenia-root-knot-nematode", + "gardenia-canker-stembranch", + "tea-mosaic-virus", + "tea-canker-stembranch", + "camellia-mosaic-virus", + "camellia-root-knot-nematode", + "camellia-canker-stembranch", + "pine-mosaic-virus", + "pine-root-knot-nematode", + "pine-canker-stembranch", + "spruce-mosaic-virus", + "spruce-root-knot-nematode", + "spruce-canker-stembranch", + "fir-mosaic-virus", + "fir-root-knot-nematode", + "fir-canker-stembranch", + "cedar-mosaic-virus", + "cedar-root-knot-nematode", + "cedar-canker-stembranch", + "juniper-mosaic-virus", + "juniper-root-knot-nematode", + "juniper-canker-stembranch", + "cypress-mosaic-virus", + "cypress-root-knot-nematode", + "cypress-canker-stembranch", + "arborvitae-mosaic-virus", + "arborvitae-root-knot-nematode", + "arborvitae-canker-stembranch", + "oak-mosaic-virus", + "oak-root-knot-nematode", + "oak-canker-stembranch", + "beech-mosaic-virus", + "beech-root-knot-nematode", + "beech-canker-stembranch", + "chestnut-mosaic-virus", + "chestnut-root-knot-nematode", + "chestnut-canker-stembranch", + "fiddle-leaf-fig-mosaic-virus", + "fiddle-leaf-fig-root-knot-nematode", + "fiddle-leaf-fig-canker-stembranch", + "rubber-tree-mosaic-virus", + "rubber-tree-root-knot-nematode", + "rubber-tree-canker-stembranch", + "weeping-fig-mosaic-virus", + "weeping-fig-root-knot-nematode", + "weeping-fig-canker-stembranch", + "fig-mosaic-virus", + "fig-root-knot-nematode", + "fig-canker-stembranch", + "mulberry-mosaic-virus", + "mulberry-root-knot-nematode", + "mulberry-canker-stembranch", + "breadfruit-mosaic-virus", + "breadfruit-root-knot-nematode", + "breadfruit-canker-stembranch", + "eucalyptus-mosaic-virus", + "eucalyptus-root-knot-nematode", + "eucalyptus-canker-stembranch", + "guava-mosaic-virus", + "guava-root-knot-nematode", + "guava-canker-stembranch", + "clove-mosaic-virus", + "clove-root-knot-nematode", + "clove-canker-stembranch", + "pineapple-mosaic-virus", + "pineapple-root-knot-nematode", + "pineapple-canker-stembranch", + "bromeliad-mosaic-virus", + "bromeliad-root-knot-nematode", + "bromeliad-canker-stembranch", + "spanish-moss-mosaic-virus", + "spanish-moss-root-knot-nematode", + "spanish-moss-canker-stembranch", + "sweet-potato-mosaic-virus", + "sweet-potato-root-knot-nematode", + "sweet-potato-canker-stembranch", + "morning-glory-mosaic-virus", + "morning-glory-root-knot-nematode", + "morning-glory-canker-stembranch", + "spinach-mosaic-virus", + "spinach-root-knot-nematode", + "spinach-canker-stembranch", + "swiss-chard-mosaic-virus", + "swiss-chard-root-knot-nematode", + "swiss-chard-canker-stembranch", + "beet-mosaic-virus", + "beet-root-knot-nematode", + "beet-canker-stembranch", + "quinoa-mosaic-virus", + "quinoa-root-knot-nematode", + "quinoa-canker-stembranch", + "amaranth-mosaic-virus", + "amaranth-root-knot-nematode", + "amaranth-canker-stembranch", + "rhubarb-mosaic-virus", + "rhubarb-root-knot-nematode", + "rhubarb-canker-stembranch", + "buckwheat-mosaic-virus", + "buckwheat-root-knot-nematode", + "buckwheat-canker-stembranch", + "papaya-mosaic-virus", + "papaya-canker-stembranch", + "olive-mosaic-virus", + "olive-root-knot-nematode", + "olive-canker-stembranch", + "jasmine-mosaic-virus", + "jasmine-root-knot-nematode", + "jasmine-canker-stembranch", + "lilac-mosaic-virus", + "lilac-root-knot-nematode", + "lilac-canker-stembranch", + "ash-mosaic-virus", + "ash-root-knot-nematode", + "ash-canker-stembranch", + "hops-mosaic-virus", + "hops-root-knot-nematode", + "hops-canker-stembranch", + "hemp-mosaic-virus", + "hemp-wilt-fusarium-or-verticillium", + "hemp-root-knot-nematode", + "hemp-canker-stembranch", + "fern-boston-root-rot-pythiumphytophthora", + "fern-boston-damping-off", + "fern-boston-gray-mold-botrytis-blight", + "fern-boston-mosaic-virus", + "fern-boston-wilt-fusarium-or-verticillium", + "fern-boston-root-knot-nematode", + "fern-boston-canker-stembranch", + "fern-maidenhair-root-rot-pythiumphytophthora", + "fern-maidenhair-damping-off", + "fern-maidenhair-gray-mold-botrytis-blight", + "fern-maidenhair-mosaic-virus", + "fern-maidenhair-wilt-fusarium-or-verticillium", + "fern-maidenhair-root-knot-nematode", + "fern-maidenhair-canker-stembranch", + "spider-plant-root-rot-pythiumphytophthora", + "spider-plant-damping-off", + "spider-plant-gray-mold-botrytis-blight", + "spider-plant-mosaic-virus", + "spider-plant-wilt-fusarium-or-verticillium", + "spider-plant-root-knot-nematode", + "spider-plant-canker-stembranch", + "zz-plant-root-rot-aroidsoverwatering", + "zz-plant-root-rot-pythiumphytophthora", + "zz-plant-damping-off", + "zz-plant-gray-mold-botrytis-blight", + "zz-plant-mosaic-virus", + "zz-plant-wilt-fusarium-or-verticillium", + "zz-plant-root-knot-nematode", + "zz-plant-canker-stembranch", + "prayer-plant-root-rot-pythiumphytophthora", + "prayer-plant-damping-off", + "prayer-plant-gray-mold-botrytis-blight", + "prayer-plant-mosaic-virus", + "prayer-plant-wilt-fusarium-or-verticillium", + "prayer-plant-root-knot-nematode", + "prayer-plant-canker-stembranch", + "calathea-root-rot-pythiumphytophthora", + "calathea-damping-off", + "calathea-gray-mold-botrytis-blight", + "calathea-mosaic-virus", + "calathea-wilt-fusarium-or-verticillium", + "calathea-root-knot-nematode", + "calathea-canker-stembranch", + "pilea-root-rot-pythiumphytophthora", + "pilea-damping-off", + "pilea-gray-mold-botrytis-blight", + "pilea-mosaic-virus", + "pilea-wilt-fusarium-or-verticillium", + "pilea-root-knot-nematode", + "pilea-canker-stembranch", + "tradescantia-root-rot-pythiumphytophthora", + "tradescantia-damping-off", + "tradescantia-gray-mold-botrytis-blight", + "tradescantia-mosaic-virus", + "tradescantia-wilt-fusarium-or-verticillium", + "tradescantia-root-knot-nematode", + "tradescantia-canker-stembranch", + "succulent-echeveria-stem-rot-succulents", + "succulent-echeveria-root-rot-pythiumphytophthora", + "succulent-echeveria-damping-off", + "succulent-echeveria-gray-mold-botrytis-blight", + "succulent-echeveria-mosaic-virus", + "succulent-echeveria-wilt-fusarium-or-verticillium", + "succulent-echeveria-root-knot-nematode", + "succulent-echeveria-canker-stembranch", + "money-tree-root-rot-pythiumphytophthora", + "money-tree-damping-off", + "money-tree-gray-mold-botrytis-blight", + "money-tree-mosaic-virus", + "money-tree-wilt-fusarium-or-verticillium", + "money-tree-root-knot-nematode", + "money-tree-canker-stembranch", + "palm-cat-root-rot-pythiumphytophthora", + "palm-cat-damping-off", + "palm-cat-gray-mold-botrytis-blight", + "palm-cat-mosaic-virus", + "palm-cat-wilt-fusarium-or-verticillium", + "palm-cat-root-knot-nematode", + "palm-cat-canker-stembranch", + "ficus-altissima-root-rot-pythiumphytophthora", + "ficus-altissima-damping-off", + "ficus-altissima-gray-mold-botrytis-blight", + "ficus-altissima-mosaic-virus", + "ficus-altissima-wilt-fusarium-or-verticillium", + "ficus-altissima-root-knot-nematode", + "ficus-altissima-canker-stembranch", + "string-of-pearls-sclerotinia-wilt-asteraceae", + "string-of-pearls-root-rot-pythiumphytophthora", + "string-of-pearls-damping-off", + "string-of-pearls-gray-mold-botrytis-blight", + "string-of-pearls-mosaic-virus", + "string-of-pearls-wilt-fusarium-or-verticillium", + "string-of-pearls-root-knot-nematode", + "string-of-pearls-canker-stembranch", + "burros-tail-stem-rot-succulents", + "burros-tail-root-rot-pythiumphytophthora", + "burros-tail-damping-off", + "burros-tail-gray-mold-botrytis-blight", + "burros-tail-mosaic-virus", + "burros-tail-wilt-fusarium-or-verticillium", + "burros-tail-root-knot-nematode", + "burros-tail-canker-stembranch", + "snake-plant-masoniana-root-rot-pythiumphytophthora", + "snake-plant-masoniana-damping-off", + "snake-plant-masoniana-gray-mold-botrytis-blight", + "snake-plant-masoniana-mosaic-virus", + "snake-plant-masoniana-wilt-fusarium-or-verticillium", + "snake-plant-masoniana-root-knot-nematode", + "snake-plant-masoniana-canker-stembranch", + "passion-fruit-root-rot-pythiumphytophthora", + "passion-fruit-damping-off", + "passion-fruit-gray-mold-botrytis-blight", + "passion-fruit-mosaic-virus", + "passion-fruit-wilt-fusarium-or-verticillium", + "passion-fruit-root-knot-nematode", + "passion-fruit-canker-stembranch", + "kiwi-root-rot-pythiumphytophthora", + "kiwi-damping-off", + "kiwi-gray-mold-botrytis-blight", + "kiwi-mosaic-virus", + "kiwi-wilt-fusarium-or-verticillium", + "kiwi-root-knot-nematode", + "kiwi-canker-stembranch", + "lychee-root-rot-pythiumphytophthora", + "lychee-damping-off", + "lychee-gray-mold-botrytis-blight", + "lychee-mosaic-virus", + "lychee-wilt-fusarium-or-verticillium", + "lychee-root-knot-nematode", + "lychee-canker-stembranch", + "rambutan-root-rot-pythiumphytophthora", + "rambutan-damping-off", + "rambutan-gray-mold-botrytis-blight", + "rambutan-mosaic-virus", + "rambutan-wilt-fusarium-or-verticillium", + "rambutan-root-knot-nematode", + "rambutan-canker-stembranch", + "jackfruit-root-rot-pythiumphytophthora", + "jackfruit-damping-off", + "jackfruit-gray-mold-botrytis-blight", + "jackfruit-mosaic-virus", + "jackfruit-wilt-fusarium-or-verticillium", + "jackfruit-root-knot-nematode", + "jackfruit-canker-stembranch", + "dragon-fruit-stem-rot-succulents", + "dragon-fruit-root-rot-pythiumphytophthora", + "dragon-fruit-damping-off", + "dragon-fruit-gray-mold-botrytis-blight", + "dragon-fruit-mosaic-virus", + "dragon-fruit-wilt-fusarium-or-verticillium", + "dragon-fruit-root-knot-nematode", + "dragon-fruit-canker-stembranch", + "pomegranate-root-rot-pythiumphytophthora", + "pomegranate-damping-off", + "pomegranate-gray-mold-botrytis-blight", + "pomegranate-mosaic-virus", + "pomegranate-wilt-fusarium-or-verticillium", + "pomegranate-root-knot-nematode", + "pomegranate-canker-stembranch", + "persimmon-root-rot-pythiumphytophthora", + "persimmon-damping-off", + "persimmon-gray-mold-botrytis-blight", + "persimmon-mosaic-virus", + "persimmon-wilt-fusarium-or-verticillium", + "persimmon-root-knot-nematode", + "persimmon-canker-stembranch", + "tulip-root-rot-pythiumphytophthora", + "tulip-damping-off", + "tulip-gray-mold-botrytis-blight", + "tulip-mosaic-virus", + "tulip-wilt-fusarium-or-verticillium", + "tulip-root-knot-nematode", + "tulip-canker-stembranch", + "daffodil-root-rot-pythiumphytophthora", + "daffodil-damping-off", + "daffodil-gray-mold-botrytis-blight", + "daffodil-mosaic-virus", + "daffodil-wilt-fusarium-or-verticillium", + "daffodil-root-knot-nematode", + "daffodil-canker-stembranch", + "iris-root-rot-pythiumphytophthora", + "iris-damping-off", + "iris-gray-mold-botrytis-blight", + "iris-mosaic-virus", + "iris-wilt-fusarium-or-verticillium", + "iris-root-knot-nematode", + "iris-canker-stembranch", + "lily-root-rot-pythiumphytophthora", + "lily-damping-off", + "lily-gray-mold-botrytis-blight", + "lily-mosaic-virus", + "lily-wilt-fusarium-or-verticillium", + "lily-root-knot-nematode", + "lily-canker-stembranch", + "peony-root-rot-pythiumphytophthora", + "peony-damping-off", + "peony-gray-mold-botrytis-blight", + "peony-mosaic-virus", + "peony-wilt-fusarium-or-verticillium", + "peony-root-knot-nematode", + "peony-canker-stembranch", + "hydrangea-root-rot-pythiumphytophthora", + "hydrangea-damping-off", + "hydrangea-gray-mold-botrytis-blight", + "hydrangea-mosaic-virus", + "hydrangea-wilt-fusarium-or-verticillium", + "hydrangea-root-knot-nematode", + "hydrangea-canker-stembranch", + "rhododendron-phytophthora-root-rot-ericaceous", + "rhododendron-root-rot-pythiumphytophthora", + "rhododendron-damping-off", + "rhododendron-gray-mold-botrytis-blight", + "rhododendron-mosaic-virus", + "rhododendron-wilt-fusarium-or-verticillium", + "rhododendron-root-knot-nematode", + "rhododendron-canker-stembranch", + "azalea-phytophthora-root-rot-ericaceous", + "azalea-root-rot-pythiumphytophthora", + "azalea-damping-off", + "azalea-gray-mold-botrytis-blight", + "azalea-mosaic-virus", + "azalea-wilt-fusarium-or-verticillium", + "azalea-root-knot-nematode", + "azalea-canker-stembranch", + "magnolia-root-rot-pythiumphytophthora", + "magnolia-damping-off", + "magnolia-gray-mold-botrytis-blight", + "magnolia-mosaic-virus", + "magnolia-wilt-fusarium-or-verticillium", + "magnolia-root-knot-nematode", + "magnolia-canker-stembranch", + "dogwood-root-rot-pythiumphytophthora", + "dogwood-damping-off", + "dogwood-gray-mold-botrytis-blight", + "dogwood-mosaic-virus", + "dogwood-wilt-fusarium-or-verticillium", + "dogwood-root-knot-nematode", + "dogwood-canker-stembranch", + "maple-root-rot-pythiumphytophthora", + "maple-damping-off", + "maple-gray-mold-botrytis-blight", + "maple-mosaic-virus", + "maple-wilt-fusarium-or-verticillium", + "maple-root-knot-nematode", + "maple-canker-stembranch", + "birch-root-rot-pythiumphytophthora", + "birch-damping-off", + "birch-gray-mold-botrytis-blight", + "birch-mosaic-virus", + "birch-wilt-fusarium-or-verticillium", + "birch-root-knot-nematode", + "birch-canker-stembranch", + "elm-root-rot-pythiumphytophthora", + "elm-damping-off", + "elm-gray-mold-botrytis-blight", + "elm-mosaic-virus", + "elm-wilt-fusarium-or-verticillium", + "elm-root-knot-nematode", + "elm-canker-stembranch", + "willow-root-rot-pythiumphytophthora", + "willow-damping-off", + "willow-gray-mold-botrytis-blight", + "willow-mosaic-virus", + "willow-wilt-fusarium-or-verticillium", + "willow-root-knot-nematode", + "willow-canker-stembranch", + "poplar-root-rot-pythiumphytophthora", + "poplar-damping-off", + "poplar-gray-mold-botrytis-blight", + "poplar-mosaic-virus", + "poplar-wilt-fusarium-or-verticillium", + "poplar-root-knot-nematode", + "poplar-canker-stembranch", + "sycamore-root-rot-pythiumphytophthora", + "sycamore-damping-off", + "sycamore-gray-mold-botrytis-blight", + "sycamore-mosaic-virus", + "sycamore-wilt-fusarium-or-verticillium", + "sycamore-root-knot-nematode", + "sycamore-canker-stembranch", + "hickory-root-rot-pythiumphytophthora", + "hickory-damping-off", + "hickory-gray-mold-botrytis-blight", + "hickory-mosaic-virus", + "hickory-wilt-fusarium-or-verticillium", + "hickory-root-knot-nematode", + "hickory-canker-stembranch", + "pecan-root-rot-pythiumphytophthora", + "pecan-damping-off", + "pecan-gray-mold-botrytis-blight", + "pecan-mosaic-virus", + "pecan-wilt-fusarium-or-verticillium", + "pecan-root-knot-nematode", + "pecan-canker-stembranch", + "walnut-root-rot-pythiumphytophthora", + "walnut-damping-off", + "walnut-gray-mold-botrytis-blight", + "walnut-mosaic-virus", + "walnut-wilt-fusarium-or-verticillium", + "walnut-root-knot-nematode", + "walnut-canker-stembranch", + "tomato-bacterial-soft-rot", + "tomato-downy-mildew-generic", + "tomato-viral-leaf-curl", + "tomato-wood-rot-decay", + "potato-bacterial-soft-rot", + "potato-downy-mildew-generic", + "potato-viral-leaf-curl", + "potato-wood-rot-decay", + "bell-pepper-bacterial-soft-rot", + "bell-pepper-downy-mildew-generic", + "bell-pepper-viral-leaf-curl", + "bell-pepper-wood-rot-decay", + "chili-pepper-bacterial-soft-rot", + "chili-pepper-downy-mildew-generic", + "chili-pepper-viral-leaf-curl", + "chili-pepper-wood-rot-decay", + "eggplant-bacterial-soft-rot", + "eggplant-downy-mildew-generic", + "eggplant-viral-leaf-curl", + "eggplant-wood-rot-decay", + "tobacco-bacterial-soft-rot", + "tobacco-downy-mildew-generic", + "tobacco-viral-leaf-curl", + "tobacco-wood-rot-decay", + "tomatillo-bacterial-soft-rot", + "tomatillo-downy-mildew-generic", + "tomatillo-viral-leaf-curl", + "tomatillo-wood-rot-decay", + "petunia-bacterial-soft-rot", + "petunia-downy-mildew-generic", + "petunia-viral-leaf-curl", + "petunia-wood-rot-decay", + "gooseberry-bacterial-soft-rot", + "gooseberry-downy-mildew-generic", + "gooseberry-viral-leaf-curl", + "gooseberry-wood-rot-decay", + "cucumber-downy-mildew-generic", + "cucumber-viral-leaf-curl", + "cucumber-wood-rot-decay", + "zucchini-bacterial-soft-rot", + "zucchini-downy-mildew-generic", + "zucchini-viral-leaf-curl", + "zucchini-wood-rot-decay", + "summer-squash-bacterial-soft-rot", + "summer-squash-downy-mildew-generic", + "summer-squash-viral-leaf-curl", + "summer-squash-wood-rot-decay", + "winter-squash-bacterial-soft-rot", + "winter-squash-downy-mildew-generic", + "winter-squash-viral-leaf-curl", + "winter-squash-wood-rot-decay", + "pumpkin-bacterial-soft-rot", + "pumpkin-downy-mildew-generic", + "pumpkin-viral-leaf-curl", + "pumpkin-wood-rot-decay", + "watermelon-downy-mildew-generic", + "watermelon-viral-leaf-curl", + "watermelon-wood-rot-decay", + "cantaloupe-bacterial-soft-rot", + "cantaloupe-downy-mildew-generic", + "cantaloupe-viral-leaf-curl", + "cantaloupe-wood-rot-decay", + "honeydew-bacterial-soft-rot", + "honeydew-downy-mildew-generic", + "honeydew-viral-leaf-curl", + "honeydew-wood-rot-decay", + "bitter-melon-bacterial-soft-rot", + "bitter-melon-downy-mildew-generic", + "bitter-melon-viral-leaf-curl", + "bitter-melon-wood-rot-decay", + "chayote-bacterial-soft-rot", + "chayote-downy-mildew-generic", + "chayote-viral-leaf-curl", + "chayote-wood-rot-decay", + "acorn-squash-bacterial-soft-rot", + "acorn-squash-downy-mildew-generic", + "acorn-squash-viral-leaf-curl", + "acorn-squash-wood-rot-decay", + "butternut-squash-bacterial-soft-rot", + "butternut-squash-downy-mildew-generic", + "butternut-squash-viral-leaf-curl", + "butternut-squash-wood-rot-decay", + "calabash-bacterial-soft-rot", + "calabash-downy-mildew-generic", + "calabash-viral-leaf-curl", + "calabash-wood-rot-decay", + "luffa-bacterial-soft-rot", + "luffa-downy-mildew-generic", + "luffa-viral-leaf-curl", + "luffa-wood-rot-decay", + "apple-bacterial-soft-rot", + "apple-downy-mildew-generic", + "apple-viral-leaf-curl", + "apple-wood-rot-decay", + "pear-bacterial-soft-rot", + "pear-downy-mildew-generic", + "pear-viral-leaf-curl", + "pear-wood-rot-decay", + "peach-bacterial-soft-rot", + "peach-downy-mildew-generic", + "peach-viral-leaf-curl", + "peach-wood-rot-decay", + "cherry-bacterial-soft-rot", + "cherry-downy-mildew-generic", + "cherry-viral-leaf-curl", + "cherry-wood-rot-decay", + "apricot-bacterial-soft-rot", + "apricot-downy-mildew-generic", + "apricot-viral-leaf-curl", + "apricot-wood-rot-decay", + "plum-bacterial-soft-rot", + "plum-downy-mildew-generic", + "plum-viral-leaf-curl", + "plum-wood-rot-decay", + "almond-bacterial-soft-rot", + "almond-downy-mildew-generic", + "almond-viral-leaf-curl", + "almond-wood-rot-decay", + "strawberry-bacterial-soft-rot", + "strawberry-downy-mildew-generic", + "strawberry-viral-leaf-curl", + "strawberry-wood-rot-decay", + "raspberry-bacterial-soft-rot", + "raspberry-downy-mildew-generic", + "raspberry-viral-leaf-curl", + "raspberry-wood-rot-decay", + "blackberry-bacterial-soft-rot", + "blackberry-downy-mildew-generic", + "blackberry-viral-leaf-curl", + "blackberry-wood-rot-decay", + "blueberry-bacterial-soft-rot", + "blueberry-downy-mildew-generic", + "blueberry-viral-leaf-curl", + "blueberry-wood-rot-decay", + "cranberry-bacterial-soft-rot", + "cranberry-downy-mildew-generic", + "cranberry-viral-leaf-curl", + "cranberry-wood-rot-decay", + "rose-bacterial-soft-rot", + "rose-downy-mildew-generic", + "rose-viral-leaf-curl", + "rose-wood-rot-decay", + "hawthorn-bacterial-soft-rot", + "hawthorn-downy-mildew-generic", + "hawthorn-viral-leaf-curl", + "hawthorn-wood-rot-decay", + "quince-bacterial-soft-rot", + "quince-downy-mildew-generic", + "quince-viral-leaf-curl", + "quince-wood-rot-decay", + "cabbage-bacterial-soft-rot", + "cabbage-downy-mildew-generic", + "cabbage-viral-leaf-curl", + "cabbage-wood-rot-decay", + "broccoli-bacterial-soft-rot", + "broccoli-downy-mildew-generic", + "broccoli-viral-leaf-curl", + "broccoli-wood-rot-decay", + "cauliflower-bacterial-soft-rot", + "cauliflower-downy-mildew-generic", + "cauliflower-viral-leaf-curl", + "cauliflower-wood-rot-decay", + "brussels-sprouts-bacterial-soft-rot", + "brussels-sprouts-downy-mildew-generic", + "brussels-sprouts-viral-leaf-curl", + "brussels-sprouts-wood-rot-decay", + "kale-bacterial-soft-rot", + "kale-downy-mildew-generic", + "kale-viral-leaf-curl", + "kale-wood-rot-decay", + "bok-choy-bacterial-soft-rot", + "bok-choy-downy-mildew-generic", + "bok-choy-viral-leaf-curl", + "bok-choy-wood-rot-decay", + "radish-bacterial-soft-rot", + "radish-downy-mildew-generic", + "radish-viral-leaf-curl", + "radish-wood-rot-decay", + "turnip-bacterial-soft-rot", + "turnip-downy-mildew-generic", + "turnip-viral-leaf-curl", + "turnip-wood-rot-decay", + "arugula-bacterial-soft-rot", + "arugula-downy-mildew-generic", + "arugula-viral-leaf-curl", + "arugula-wood-rot-decay", + "collard-greens-bacterial-soft-rot", + "collard-greens-downy-mildew-generic", + "collard-greens-viral-leaf-curl", + "collard-greens-wood-rot-decay", + "mustard-greens-bacterial-soft-rot", + "mustard-greens-downy-mildew-generic", + "mustard-greens-viral-leaf-curl", + "mustard-greens-wood-rot-decay", + "horseradish-bacterial-soft-rot", + "horseradish-downy-mildew-generic", + "horseradish-viral-leaf-curl", + "horseradish-wood-rot-decay", + "wasabi-bacterial-soft-rot", + "wasabi-downy-mildew-generic", + "wasabi-viral-leaf-curl", + "wasabi-wood-rot-decay", + "green-bean-bacterial-soft-rot", + "green-bean-downy-mildew-generic", + "green-bean-viral-leaf-curl", + "green-bean-wood-rot-decay", + "soybean-bacterial-soft-rot", + "soybean-downy-mildew-generic", + "soybean-viral-leaf-curl", + "soybean-wood-rot-decay", + "peanut-bacterial-soft-rot", + "peanut-downy-mildew-generic", + "peanut-viral-leaf-curl", + "peanut-wood-rot-decay", + "chickpea-bacterial-soft-rot", + "chickpea-downy-mildew-generic", + "chickpea-viral-leaf-curl", + "chickpea-wood-rot-decay", + "lentil-bacterial-soft-rot", + "lentil-downy-mildew-generic", + "lentil-viral-leaf-curl", + "lentil-wood-rot-decay", + "faba-bean-bacterial-soft-rot", + "faba-bean-downy-mildew-generic", + "faba-bean-viral-leaf-curl", + "faba-bean-wood-rot-decay", + "cowpea-bacterial-soft-rot", + "cowpea-downy-mildew-generic", + "cowpea-viral-leaf-curl", + "cowpea-wood-rot-decay", + "pigeon-pea-bacterial-soft-rot", + "pigeon-pea-downy-mildew-generic", + "pigeon-pea-viral-leaf-curl", + "pigeon-pea-wood-rot-decay", + "alfalfa-bacterial-soft-rot", + "alfalfa-downy-mildew-generic", + "alfalfa-viral-leaf-curl", + "alfalfa-wood-rot-decay", + "clover-bacterial-soft-rot", + "clover-downy-mildew-generic", + "clover-viral-leaf-curl", + "clover-wood-rot-decay", + "peas-bacterial-soft-rot", + "peas-downy-mildew-generic", + "peas-viral-leaf-curl", + "peas-wood-rot-decay", + "lupine-bacterial-soft-rot", + "lupine-downy-mildew-generic", + "lupine-viral-leaf-curl", + "lupine-wood-rot-decay", + "wisteria-bacterial-soft-rot", + "wisteria-downy-mildew-generic", + "wisteria-viral-leaf-curl", + "wisteria-wood-rot-decay", + "robinia-bacterial-soft-rot", + "robinia-downy-mildew-generic", + "robinia-viral-leaf-curl", + "robinia-wood-rot-decay", + "corn-bacterial-soft-rot", + "corn-downy-mildew-generic", + "corn-viral-leaf-curl", + "corn-wood-rot-decay", + "wheat-bacterial-soft-rot", + "wheat-downy-mildew-generic", + "wheat-viral-leaf-curl", + "wheat-wood-rot-decay", + "rice-bacterial-soft-rot", + "rice-downy-mildew-generic", + "rice-viral-leaf-curl", + "rice-wood-rot-decay", + "barley-bacterial-soft-rot", + "barley-downy-mildew-generic", + "barley-viral-leaf-curl", + "barley-wood-rot-decay", + "oats-bacterial-soft-rot", + "oats-downy-mildew-generic", + "oats-viral-leaf-curl", + "oats-wood-rot-decay", + "sorghum-bacterial-soft-rot", + "sorghum-downy-mildew-generic", + "sorghum-viral-leaf-curl", + "sorghum-wood-rot-decay", + "sugarcane-bacterial-soft-rot", + "sugarcane-downy-mildew-generic", + "sugarcane-viral-leaf-curl", + "sugarcane-wood-rot-decay", + "bamboo-bacterial-soft-rot", + "bamboo-downy-mildew-generic", + "bamboo-viral-leaf-curl", + "bamboo-wood-rot-decay", + "turfgrass-bacterial-soft-rot", + "turfgrass-downy-mildew-generic", + "turfgrass-viral-leaf-curl", + "turfgrass-wood-rot-decay", + "millet-bacterial-soft-rot", + "millet-downy-mildew-generic", + "millet-viral-leaf-curl", + "millet-wood-rot-decay", + "rye-bacterial-soft-rot", + "rye-downy-mildew-generic", + "rye-viral-leaf-curl", + "rye-wood-rot-decay", + "sunflower-bacterial-soft-rot", + "sunflower-downy-mildew-generic", + "sunflower-viral-leaf-curl", + "sunflower-wood-rot-decay", + "lettuce-downy-mildew-generic", + "lettuce-viral-leaf-curl", + "lettuce-wood-rot-decay", + "artichoke-bacterial-soft-rot", + "artichoke-downy-mildew-generic", + "artichoke-viral-leaf-curl", + "artichoke-wood-rot-decay", + "chicory-bacterial-soft-rot", + "chicory-downy-mildew-generic", + "chicory-viral-leaf-curl", + "chicory-wood-rot-decay", + "endive-bacterial-soft-rot", + "endive-downy-mildew-generic", + "endive-viral-leaf-curl", + "endive-wood-rot-decay", + "daisy-bacterial-soft-rot", + "daisy-downy-mildew-generic", + "daisy-viral-leaf-curl", + "daisy-wood-rot-decay", + "marigold-bacterial-soft-rot", + "marigold-downy-mildew-generic", + "marigold-viral-leaf-curl", + "marigold-wood-rot-decay", + "zinnia-bacterial-soft-rot", + "zinnia-downy-mildew-generic", + "zinnia-viral-leaf-curl", + "zinnia-wood-rot-decay", + "chrysanthemum-bacterial-soft-rot", + "chrysanthemum-downy-mildew-generic", + "chrysanthemum-viral-leaf-curl", + "chrysanthemum-wood-rot-decay", + "dahlia-bacterial-soft-rot", + "dahlia-downy-mildew-generic", + "dahlia-viral-leaf-curl", + "dahlia-wood-rot-decay", + "calendula-bacterial-soft-rot", + "calendula-downy-mildew-generic", + "calendula-viral-leaf-curl", + "calendula-wood-rot-decay", + "echinacea-bacterial-soft-rot", + "echinacea-downy-mildew-generic", + "echinacea-viral-leaf-curl", + "echinacea-wood-rot-decay", + "yarrow-bacterial-soft-rot", + "yarrow-downy-mildew-generic", + "yarrow-viral-leaf-curl", + "yarrow-wood-rot-decay", + "tarragon-bacterial-soft-rot", + "tarragon-downy-mildew-generic", + "tarragon-viral-leaf-curl", + "tarragon-wood-rot-decay", + "stevia-bacterial-soft-rot", + "stevia-downy-mildew-generic", + "stevia-viral-leaf-curl", + "stevia-wood-rot-decay", + "basil-bacterial-soft-rot", + "basil-downy-mildew-generic", + "basil-viral-leaf-curl", + "basil-wood-rot-decay", + "mint-bacterial-soft-rot", + "mint-downy-mildew-generic", + "mint-viral-leaf-curl", + "mint-wood-rot-decay", + "lavender-bacterial-soft-rot", + "lavender-downy-mildew-generic", + "lavender-viral-leaf-curl", + "lavender-wood-rot-decay", + "rosemary-bacterial-soft-rot", + "rosemary-downy-mildew-generic", + "rosemary-viral-leaf-curl", + "rosemary-wood-rot-decay", + "thyme-bacterial-soft-rot", + "thyme-downy-mildew-generic", + "thyme-viral-leaf-curl", + "thyme-wood-rot-decay", + "oregano-bacterial-soft-rot", + "oregano-downy-mildew-generic", + "oregano-viral-leaf-curl", + "oregano-wood-rot-decay", + "sage-bacterial-soft-rot", + "sage-downy-mildew-generic", + "sage-viral-leaf-curl", + "sage-wood-rot-decay", + "lemon-balm-bacterial-soft-rot", + "lemon-balm-downy-mildew-generic", + "lemon-balm-viral-leaf-curl", + "lemon-balm-wood-rot-decay", + "catnip-bacterial-soft-rot", + "catnip-downy-mildew-generic", + "catnip-viral-leaf-curl", + "catnip-wood-rot-decay", + "coleus-bacterial-soft-rot", + "coleus-downy-mildew-generic", + "coleus-viral-leaf-curl", + "coleus-wood-rot-decay", + "carrot-downy-mildew-generic", + "carrot-viral-leaf-curl", + "carrot-wood-rot-decay", + "celery-bacterial-soft-rot", + "celery-downy-mildew-generic", + "celery-viral-leaf-curl", + "celery-wood-rot-decay", + "parsley-bacterial-soft-rot", + "parsley-downy-mildew-generic", + "parsley-viral-leaf-curl", + "parsley-wood-rot-decay", + "cilantro-bacterial-soft-rot", + "cilantro-downy-mildew-generic", + "cilantro-viral-leaf-curl", + "cilantro-wood-rot-decay", + "dill-bacterial-soft-rot", + "dill-downy-mildew-generic", + "dill-viral-leaf-curl", + "dill-wood-rot-decay", + "fennel-bacterial-soft-rot", + "fennel-downy-mildew-generic", + "fennel-viral-leaf-curl", + "fennel-wood-rot-decay", + "parsnip-bacterial-soft-rot", + "parsnip-downy-mildew-generic", + "parsnip-viral-leaf-curl", + "parsnip-wood-rot-decay", + "cumin-bacterial-soft-rot", + "cumin-downy-mildew-generic", + "cumin-viral-leaf-curl", + "cumin-wood-rot-decay", + "onion-bacterial-soft-rot", + "onion-downy-mildew-generic", + "onion-viral-leaf-curl", + "onion-wood-rot-decay", + "garlic-bacterial-soft-rot", + "garlic-downy-mildew-generic", + "garlic-viral-leaf-curl", + "garlic-wood-rot-decay", + "leek-bacterial-soft-rot", + "leek-downy-mildew-generic", + "leek-viral-leaf-curl", + "leek-wood-rot-decay", + "shallot-bacterial-soft-rot", + "shallot-downy-mildew-generic", + "shallot-viral-leaf-curl", + "shallot-wood-rot-decay", + "chive-bacterial-soft-rot", + "chive-downy-mildew-generic", + "chive-viral-leaf-curl", + "chive-wood-rot-decay", + "monstera-bacterial-soft-rot", + "monstera-downy-mildew-generic", + "monstera-viral-leaf-curl", + "monstera-wood-rot-decay", + "pothos-downy-mildew-generic", + "pothos-viral-leaf-curl", + "pothos-wood-rot-decay", + "peace-lily-bacterial-soft-rot", + "peace-lily-downy-mildew-generic", + "peace-lily-viral-leaf-curl", + "peace-lily-wood-rot-decay", + "philodendron-bacterial-soft-rot", + "philodendron-downy-mildew-generic", + "philodendron-viral-leaf-curl", + "philodendron-wood-rot-decay", + "anthurium-bacterial-soft-rot", + "anthurium-downy-mildew-generic", + "anthurium-viral-leaf-curl", + "anthurium-wood-rot-decay", + "alocasia-bacterial-soft-rot", + "alocasia-downy-mildew-generic", + "alocasia-viral-leaf-curl", + "alocasia-wood-rot-decay", + "caladium-bacterial-soft-rot", + "caladium-downy-mildew-generic", + "caladium-viral-leaf-curl", + "caladium-wood-rot-decay", + "aglaonema-bacterial-soft-rot", + "aglaonema-downy-mildew-generic", + "aglaonema-viral-leaf-curl", + "aglaonema-wood-rot-decay", + "dieffenbachia-bacterial-soft-rot", + "dieffenbachia-downy-mildew-generic", + "dieffenbachia-viral-leaf-curl", + "dieffenbachia-wood-rot-decay", + "spathiphyllum-bacterial-soft-rot", + "spathiphyllum-downy-mildew-generic", + "spathiphyllum-viral-leaf-curl", + "spathiphyllum-wood-rot-decay", + "asparagus-bacterial-soft-rot", + "asparagus-downy-mildew-generic", + "asparagus-viral-leaf-curl", + "asparagus-wood-rot-decay", + "snake-plant-bacterial-soft-rot", + "snake-plant-downy-mildew-generic", + "snake-plant-viral-leaf-curl", + "snake-plant-wood-rot-decay", + "yucca-bacterial-soft-rot", + "yucca-downy-mildew-generic", + "yucca-viral-leaf-curl", + "yucca-wood-rot-decay", + "dracaena-bacterial-soft-rot", + "dracaena-downy-mildew-generic", + "dracaena-viral-leaf-curl", + "dracaena-wood-rot-decay", + "lily-of-the-valley-bacterial-soft-rot", + "lily-of-the-valley-downy-mildew-generic", + "lily-of-the-valley-viral-leaf-curl", + "lily-of-the-valley-wood-rot-decay", + "hosta-bacterial-soft-rot", + "hosta-downy-mildew-generic", + "hosta-viral-leaf-curl", + "hosta-wood-rot-decay", + "orchid-phalaenopsis-bacterial-soft-rot", + "orchid-phalaenopsis-downy-mildew-generic", + "orchid-phalaenopsis-viral-leaf-curl", + "orchid-phalaenopsis-wood-rot-decay", + "orchid-cattleya-bacterial-soft-rot", + "orchid-cattleya-downy-mildew-generic", + "orchid-cattleya-viral-leaf-curl", + "orchid-cattleya-wood-rot-decay", + "orchid-dendrobium-bacterial-soft-rot", + "orchid-dendrobium-downy-mildew-generic", + "orchid-dendrobium-viral-leaf-curl", + "orchid-dendrobium-wood-rot-decay", + "orchid-oncidium-bacterial-soft-rot", + "orchid-oncidium-downy-mildew-generic", + "orchid-oncidium-viral-leaf-curl", + "orchid-oncidium-wood-rot-decay", + "vanilla-bacterial-soft-rot", + "vanilla-downy-mildew-generic", + "vanilla-viral-leaf-curl", + "vanilla-wood-rot-decay", + "prickly-pear-bacterial-soft-rot", + "prickly-pear-downy-mildew-generic", + "prickly-pear-viral-leaf-curl", + "prickly-pear-wood-rot-decay", + "barrel-cactus-bacterial-soft-rot", + "barrel-cactus-downy-mildew-generic", + "barrel-cactus-viral-leaf-curl", + "barrel-cactus-wood-rot-decay", + "christmas-cactus-bacterial-soft-rot", + "christmas-cactus-downy-mildew-generic", + "christmas-cactus-viral-leaf-curl", + "christmas-cactus-wood-rot-decay", + "saguaro-bacterial-soft-rot", + "saguaro-downy-mildew-generic", + "saguaro-viral-leaf-curl", + "saguaro-wood-rot-decay", + "aloe-vera-bacterial-soft-rot", + "aloe-vera-downy-mildew-generic", + "aloe-vera-viral-leaf-curl", + "aloe-vera-wood-rot-decay", + "agave-bacterial-soft-rot", + "agave-downy-mildew-generic", + "agave-viral-leaf-curl", + "agave-wood-rot-decay", + "echeveria-bacterial-soft-rot", + "echeveria-downy-mildew-generic", + "echeveria-viral-leaf-curl", + "echeveria-wood-rot-decay", + "jade-plant-bacterial-soft-rot", + "jade-plant-downy-mildew-generic", + "jade-plant-viral-leaf-curl", + "jade-plant-wood-rot-decay", + "sedum-bacterial-soft-rot", + "sedum-downy-mildew-generic", + "sedum-viral-leaf-curl", + "sedum-wood-rot-decay", + "haworthia-bacterial-soft-rot", + "haworthia-downy-mildew-generic", + "haworthia-viral-leaf-curl", + "haworthia-wood-rot-decay", + "poinsettia-bacterial-soft-rot", + "poinsettia-downy-mildew-generic", + "poinsettia-viral-leaf-curl", + "poinsettia-wood-rot-decay", + "cassava-bacterial-soft-rot", + "cassava-downy-mildew-generic", + "cassava-viral-leaf-curl", + "cassava-wood-rot-decay", + "castor-bean-bacterial-soft-rot", + "castor-bean-downy-mildew-generic", + "castor-bean-viral-leaf-curl", + "castor-bean-wood-rot-decay", + "crown-of-thorns-bacterial-soft-rot", + "crown-of-thorns-downy-mildew-generic", + "crown-of-thorns-viral-leaf-curl", + "crown-of-thorns-wood-rot-decay", + "orange-bacterial-soft-rot", + "orange-downy-mildew-generic", + "orange-viral-leaf-curl", + "orange-wood-rot-decay", + "lemon-bacterial-soft-rot", + "lemon-downy-mildew-generic", + "lemon-viral-leaf-curl", + "lemon-wood-rot-decay", + "lime-bacterial-soft-rot", + "lime-downy-mildew-generic", + "lime-viral-leaf-curl", + "lime-wood-rot-decay", + "grapefruit-bacterial-soft-rot", + "grapefruit-downy-mildew-generic", + "grapefruit-viral-leaf-curl", + "grapefruit-wood-rot-decay", + "mandarin-bacterial-soft-rot", + "mandarin-downy-mildew-generic", + "mandarin-viral-leaf-curl", + "mandarin-wood-rot-decay", + "kumquat-bacterial-soft-rot", + "kumquat-downy-mildew-generic", + "kumquat-viral-leaf-curl", + "kumquat-wood-rot-decay", + "grape-bacterial-soft-rot", + "grape-downy-mildew-generic", + "grape-viral-leaf-curl", + "grape-wood-rot-decay", + "muscadine-bacterial-soft-rot", + "muscadine-downy-mildew-generic", + "muscadine-viral-leaf-curl", + "muscadine-wood-rot-decay", + "banana-bacterial-soft-rot", + "banana-downy-mildew-generic", + "banana-viral-leaf-curl", + "banana-wood-rot-decay", + "plantain-bacterial-soft-rot", + "plantain-downy-mildew-generic", + "plantain-viral-leaf-curl", + "plantain-wood-rot-decay", + "bird-of-paradise-bacterial-soft-rot", + "bird-of-paradise-downy-mildew-generic", + "bird-of-paradise-viral-leaf-curl", + "bird-of-paradise-wood-rot-decay", + "avocado-bacterial-soft-rot", + "avocado-downy-mildew-generic", + "avocado-viral-leaf-curl", + "avocado-wood-rot-decay", + "cinnamon-bacterial-soft-rot", + "cinnamon-downy-mildew-generic", + "cinnamon-viral-leaf-curl", + "cinnamon-wood-rot-decay", + "bay-laurel-bacterial-soft-rot", + "bay-laurel-downy-mildew-generic", + "bay-laurel-viral-leaf-curl", + "bay-laurel-wood-rot-decay", + "cocoa-bacterial-soft-rot", + "cocoa-downy-mildew-generic", + "cocoa-viral-leaf-curl", + "cocoa-wood-rot-decay", + "cotton-bacterial-soft-rot", + "cotton-downy-mildew-generic", + "cotton-viral-leaf-curl", + "cotton-wood-rot-decay", + "okra-bacterial-soft-rot", + "okra-downy-mildew-generic", + "okra-viral-leaf-curl", + "okra-wood-rot-decay", + "hibiscus-bacterial-soft-rot", + "hibiscus-downy-mildew-generic", + "hibiscus-viral-leaf-curl", + "hibiscus-wood-rot-decay", + "hollyhock-bacterial-soft-rot", + "hollyhock-downy-mildew-generic", + "hollyhock-viral-leaf-curl", + "hollyhock-wood-rot-decay", + "baobab-bacterial-soft-rot", + "baobab-downy-mildew-generic", + "baobab-viral-leaf-curl", + "baobab-wood-rot-decay", + "durian-bacterial-soft-rot", + "durian-downy-mildew-generic", + "durian-viral-leaf-curl", + "durian-wood-rot-decay", + "coconut-bacterial-soft-rot", + "coconut-downy-mildew-generic", + "coconut-viral-leaf-curl", + "coconut-wood-rot-decay", + "oil-palm-bacterial-soft-rot", + "oil-palm-downy-mildew-generic", + "oil-palm-viral-leaf-curl", + "oil-palm-wood-rot-decay", + "date-palm-bacterial-soft-rot", + "date-palm-downy-mildew-generic", + "date-palm-viral-leaf-curl", + "date-palm-wood-rot-decay", + "palm-areca-bacterial-soft-rot", + "palm-areca-downy-mildew-generic", + "palm-areca-viral-leaf-curl", + "palm-areca-wood-rot-decay", + "palm-parlor-bacterial-soft-rot", + "palm-parlor-downy-mildew-generic", + "palm-parlor-viral-leaf-curl", + "palm-parlor-wood-rot-decay", + "palm-kentia-bacterial-soft-rot", + "palm-kentia-downy-mildew-generic", + "palm-kentia-viral-leaf-curl", + "palm-kentia-wood-rot-decay", + "mango-bacterial-soft-rot", + "mango-downy-mildew-generic", + "mango-viral-leaf-curl", + "mango-wood-rot-decay", + "cashew-bacterial-soft-rot", + "cashew-downy-mildew-generic", + "cashew-viral-leaf-curl", + "cashew-wood-rot-decay", + "pistachio-bacterial-soft-rot", + "pistachio-downy-mildew-generic", + "pistachio-viral-leaf-curl", + "pistachio-wood-rot-decay", + "poison-ivy-bacterial-soft-rot", + "poison-ivy-downy-mildew-generic", + "poison-ivy-viral-leaf-curl", + "poison-ivy-wood-rot-decay", + "coffee-bacterial-soft-rot", + "coffee-downy-mildew-generic", + "coffee-viral-leaf-curl", + "coffee-wood-rot-decay", + "gardenia-bacterial-soft-rot", + "gardenia-downy-mildew-generic", + "gardenia-viral-leaf-curl", + "gardenia-wood-rot-decay", + "tea-bacterial-soft-rot", + "tea-downy-mildew-generic", + "tea-viral-leaf-curl", + "tea-wood-rot-decay", + "camellia-bacterial-soft-rot", + "camellia-downy-mildew-generic", + "camellia-viral-leaf-curl", + "camellia-wood-rot-decay", + "pine-bacterial-soft-rot", + "pine-downy-mildew-generic", + "pine-viral-leaf-curl", + "pine-wood-rot-decay", + "spruce-bacterial-soft-rot", + "spruce-downy-mildew-generic", + "spruce-viral-leaf-curl", + "spruce-wood-rot-decay", + "fir-bacterial-soft-rot", + "fir-downy-mildew-generic", + "fir-viral-leaf-curl", + "fir-wood-rot-decay", + "cedar-bacterial-soft-rot", + "cedar-downy-mildew-generic", + "cedar-viral-leaf-curl", + "cedar-wood-rot-decay", + "juniper-bacterial-soft-rot", + "juniper-downy-mildew-generic", + "juniper-viral-leaf-curl", + "juniper-wood-rot-decay", + "cypress-bacterial-soft-rot", + "cypress-downy-mildew-generic", + "cypress-viral-leaf-curl", + "cypress-wood-rot-decay", + "arborvitae-bacterial-soft-rot", + "arborvitae-downy-mildew-generic", + "arborvitae-viral-leaf-curl", + "arborvitae-wood-rot-decay", + "oak-bacterial-soft-rot", + "oak-downy-mildew-generic", + "oak-viral-leaf-curl", + "oak-wood-rot-decay", + "beech-bacterial-soft-rot", + "beech-downy-mildew-generic", + "beech-viral-leaf-curl", + "beech-wood-rot-decay", + "chestnut-bacterial-soft-rot", + "chestnut-downy-mildew-generic", + "chestnut-viral-leaf-curl", + "chestnut-wood-rot-decay", + "fiddle-leaf-fig-bacterial-soft-rot", + "fiddle-leaf-fig-downy-mildew-generic", + "fiddle-leaf-fig-viral-leaf-curl", + "fiddle-leaf-fig-wood-rot-decay", + "rubber-tree-bacterial-soft-rot", + "rubber-tree-downy-mildew-generic", + "rubber-tree-viral-leaf-curl", + "rubber-tree-wood-rot-decay", + "weeping-fig-bacterial-soft-rot", + "weeping-fig-downy-mildew-generic", + "weeping-fig-viral-leaf-curl", + "weeping-fig-wood-rot-decay", + "fig-bacterial-soft-rot", + "fig-downy-mildew-generic", + "fig-viral-leaf-curl", + "fig-wood-rot-decay", + "mulberry-bacterial-soft-rot", + "mulberry-downy-mildew-generic", + "mulberry-viral-leaf-curl", + "mulberry-wood-rot-decay", + "breadfruit-bacterial-soft-rot", + "breadfruit-downy-mildew-generic", + "breadfruit-viral-leaf-curl", + "breadfruit-wood-rot-decay", + "eucalyptus-bacterial-soft-rot", + "eucalyptus-downy-mildew-generic", + "eucalyptus-viral-leaf-curl", + "eucalyptus-wood-rot-decay", + "guava-bacterial-soft-rot", + "guava-downy-mildew-generic", + "guava-viral-leaf-curl", + "guava-wood-rot-decay", + "clove-bacterial-soft-rot", + "clove-downy-mildew-generic", + "clove-viral-leaf-curl", + "clove-wood-rot-decay", + "pineapple-bacterial-soft-rot", + "pineapple-downy-mildew-generic", + "pineapple-viral-leaf-curl", + "pineapple-wood-rot-decay", + "bromeliad-bacterial-soft-rot", + "bromeliad-downy-mildew-generic", + "bromeliad-viral-leaf-curl", + "bromeliad-wood-rot-decay", + "spanish-moss-bacterial-soft-rot", + "spanish-moss-downy-mildew-generic", + "spanish-moss-viral-leaf-curl", + "spanish-moss-wood-rot-decay", + "sweet-potato-bacterial-soft-rot", + "sweet-potato-downy-mildew-generic", + "sweet-potato-viral-leaf-curl", + "sweet-potato-wood-rot-decay", + "morning-glory-bacterial-soft-rot", + "morning-glory-downy-mildew-generic", + "morning-glory-viral-leaf-curl", + "morning-glory-wood-rot-decay", + "spinach-downy-mildew-generic", + "spinach-viral-leaf-curl", + "spinach-wood-rot-decay", + "swiss-chard-bacterial-soft-rot", + "swiss-chard-downy-mildew-generic", + "swiss-chard-viral-leaf-curl", + "swiss-chard-wood-rot-decay", + "beet-bacterial-soft-rot", + "beet-downy-mildew-generic", + "beet-viral-leaf-curl", + "beet-wood-rot-decay", + "quinoa-bacterial-soft-rot", + "quinoa-downy-mildew-generic", + "quinoa-viral-leaf-curl", + "quinoa-wood-rot-decay", + "amaranth-bacterial-soft-rot", + "amaranth-downy-mildew-generic", + "amaranth-viral-leaf-curl", + "amaranth-wood-rot-decay", + "rhubarb-bacterial-soft-rot", + "rhubarb-downy-mildew-generic", + "rhubarb-viral-leaf-curl", + "rhubarb-wood-rot-decay", + "buckwheat-bacterial-soft-rot", + "buckwheat-downy-mildew-generic", + "buckwheat-viral-leaf-curl", + "buckwheat-wood-rot-decay", + "papaya-bacterial-soft-rot", + "papaya-downy-mildew-generic", + "papaya-viral-leaf-curl", + "papaya-wood-rot-decay", + "olive-bacterial-soft-rot", + "olive-downy-mildew-generic", + "olive-viral-leaf-curl", + "olive-wood-rot-decay", + "jasmine-bacterial-soft-rot", + "jasmine-downy-mildew-generic", + "jasmine-viral-leaf-curl", + "jasmine-wood-rot-decay", + "lilac-bacterial-soft-rot", + "lilac-downy-mildew-generic", + "lilac-viral-leaf-curl", + "lilac-wood-rot-decay", + "ash-bacterial-soft-rot", + "ash-downy-mildew-generic", + "ash-viral-leaf-curl", + "ash-wood-rot-decay", + "hops-bacterial-soft-rot", + "hops-downy-mildew-generic", + "hops-viral-leaf-curl", + "hops-wood-rot-decay", + "hemp-bacterial-soft-rot", + "hemp-downy-mildew-generic", + "hemp-viral-leaf-curl", + "hemp-wood-rot-decay", + "fern-boston-bacterial-soft-rot", + "fern-boston-downy-mildew-generic", + "fern-boston-viral-leaf-curl", + "fern-boston-wood-rot-decay", + "fern-maidenhair-bacterial-soft-rot", + "fern-maidenhair-downy-mildew-generic", + "fern-maidenhair-viral-leaf-curl", + "fern-maidenhair-wood-rot-decay", + "spider-plant-bacterial-soft-rot", + "spider-plant-downy-mildew-generic", + "spider-plant-viral-leaf-curl", + "spider-plant-wood-rot-decay", + "zz-plant-bacterial-soft-rot", + "zz-plant-downy-mildew-generic", + "zz-plant-viral-leaf-curl", + "zz-plant-wood-rot-decay", + "prayer-plant-bacterial-soft-rot", + "prayer-plant-downy-mildew-generic", + "prayer-plant-viral-leaf-curl", + "prayer-plant-wood-rot-decay", + "calathea-bacterial-soft-rot", + "calathea-downy-mildew-generic", + "calathea-viral-leaf-curl", + "calathea-wood-rot-decay", + "pilea-bacterial-soft-rot", + "pilea-downy-mildew-generic", + "pilea-viral-leaf-curl", + "pilea-wood-rot-decay", + "tradescantia-bacterial-soft-rot", + "tradescantia-downy-mildew-generic", + "tradescantia-viral-leaf-curl", + "tradescantia-wood-rot-decay", + "succulent-echeveria-bacterial-soft-rot", + "succulent-echeveria-downy-mildew-generic", + "succulent-echeveria-viral-leaf-curl", + "succulent-echeveria-wood-rot-decay", + "money-tree-bacterial-soft-rot", + "money-tree-downy-mildew-generic", + "money-tree-viral-leaf-curl", + "money-tree-wood-rot-decay", + "palm-cat-bacterial-soft-rot", + "palm-cat-downy-mildew-generic", + "palm-cat-viral-leaf-curl", + "palm-cat-wood-rot-decay", + "ficus-altissima-bacterial-soft-rot", + "ficus-altissima-downy-mildew-generic", + "ficus-altissima-viral-leaf-curl", + "ficus-altissima-wood-rot-decay", + "string-of-pearls-bacterial-soft-rot", + "string-of-pearls-downy-mildew-generic", + "string-of-pearls-viral-leaf-curl", + "string-of-pearls-wood-rot-decay", + "burros-tail-bacterial-soft-rot", + "burros-tail-downy-mildew-generic", + "burros-tail-viral-leaf-curl", + "burros-tail-wood-rot-decay", + "snake-plant-masoniana-bacterial-soft-rot", + "snake-plant-masoniana-downy-mildew-generic", + "snake-plant-masoniana-viral-leaf-curl", + "snake-plant-masoniana-wood-rot-decay", + "passion-fruit-bacterial-soft-rot", + "passion-fruit-downy-mildew-generic", + "passion-fruit-viral-leaf-curl", + "passion-fruit-wood-rot-decay", + "kiwi-bacterial-soft-rot", + "kiwi-downy-mildew-generic", + "kiwi-viral-leaf-curl", + "kiwi-wood-rot-decay", + "lychee-bacterial-soft-rot", + "lychee-downy-mildew-generic", + "lychee-viral-leaf-curl", + "lychee-wood-rot-decay", + "rambutan-bacterial-soft-rot", + "rambutan-downy-mildew-generic", + "rambutan-viral-leaf-curl", + "rambutan-wood-rot-decay", + "jackfruit-bacterial-soft-rot", + "jackfruit-downy-mildew-generic", + "jackfruit-viral-leaf-curl", + "jackfruit-wood-rot-decay", + "dragon-fruit-bacterial-soft-rot", + "dragon-fruit-downy-mildew-generic", + "dragon-fruit-viral-leaf-curl", + "dragon-fruit-wood-rot-decay", + "pomegranate-bacterial-soft-rot", + "pomegranate-downy-mildew-generic", + "pomegranate-viral-leaf-curl", + "pomegranate-wood-rot-decay", + "persimmon-bacterial-soft-rot", + "persimmon-downy-mildew-generic", + "persimmon-viral-leaf-curl", + "persimmon-wood-rot-decay", + "tulip-bacterial-soft-rot", + "tulip-downy-mildew-generic", + "tulip-viral-leaf-curl", + "tulip-wood-rot-decay", + "daffodil-bacterial-soft-rot", + "daffodil-downy-mildew-generic", + "daffodil-viral-leaf-curl", + "daffodil-wood-rot-decay", + "iris-bacterial-soft-rot", + "iris-downy-mildew-generic", + "iris-viral-leaf-curl", + "iris-wood-rot-decay", + "lily-bacterial-soft-rot", + "lily-downy-mildew-generic", + "lily-viral-leaf-curl", + "lily-wood-rot-decay", + "peony-bacterial-soft-rot", + "peony-downy-mildew-generic", + "peony-viral-leaf-curl", + "peony-wood-rot-decay", + "hydrangea-bacterial-soft-rot", + "hydrangea-downy-mildew-generic", + "hydrangea-viral-leaf-curl", + "hydrangea-wood-rot-decay", + "rhododendron-bacterial-soft-rot", + "rhododendron-downy-mildew-generic", + "rhododendron-viral-leaf-curl", + "rhododendron-wood-rot-decay", + "azalea-bacterial-soft-rot", + "azalea-downy-mildew-generic", + "azalea-viral-leaf-curl", + "azalea-wood-rot-decay", + "magnolia-bacterial-soft-rot", + "magnolia-downy-mildew-generic", + "magnolia-viral-leaf-curl", + "magnolia-wood-rot-decay", + "dogwood-bacterial-soft-rot", + "dogwood-downy-mildew-generic", + "dogwood-viral-leaf-curl", + "dogwood-wood-rot-decay", + "maple-bacterial-soft-rot", + "maple-downy-mildew-generic", + "maple-viral-leaf-curl", + "maple-wood-rot-decay", + "birch-bacterial-soft-rot", + "birch-downy-mildew-generic", + "birch-viral-leaf-curl", + "birch-wood-rot-decay", + "elm-bacterial-soft-rot", + "elm-downy-mildew-generic", + "elm-viral-leaf-curl", + "elm-wood-rot-decay", + "willow-bacterial-soft-rot", + "willow-downy-mildew-generic", + "willow-viral-leaf-curl", + "willow-wood-rot-decay", + "poplar-bacterial-soft-rot", + "poplar-downy-mildew-generic", + "poplar-viral-leaf-curl", + "poplar-wood-rot-decay", + "sycamore-bacterial-soft-rot", + "sycamore-downy-mildew-generic", + "sycamore-viral-leaf-curl", + "sycamore-wood-rot-decay", + "hickory-bacterial-soft-rot", + "hickory-downy-mildew-generic", + "hickory-viral-leaf-curl", + "hickory-wood-rot-decay", + "pecan-bacterial-soft-rot", + "pecan-downy-mildew-generic", + "pecan-viral-leaf-curl", + "pecan-wood-rot-decay", + "walnut-bacterial-soft-rot", + "walnut-downy-mildew-generic", + "walnut-viral-leaf-curl", + "walnut-wood-rot-decay", + "fern-staghorn-root-rot-pythiumphytophthora", + "fern-staghorn-damping-off", + "fern-staghorn-gray-mold-botrytis-blight", + "fern-staghorn-mosaic-virus", + "fern-staghorn-wilt-fusarium-or-verticillium", + "fern-staghorn-root-knot-nematode", + "fern-staghorn-canker-stembranch", + "fern-staghorn-bacterial-soft-rot", + "fern-staghorn-downy-mildew-generic", + "fern-staghorn-viral-leaf-curl", + "fern-staghorn-wood-rot-decay", + "fern-birds-nest-root-rot-pythiumphytophthora", + "fern-birds-nest-damping-off", + "fern-birds-nest-gray-mold-botrytis-blight", + "fern-birds-nest-mosaic-virus", + "fern-birds-nest-wilt-fusarium-or-verticillium", + "fern-birds-nest-root-knot-nematode", + "fern-birds-nest-canker-stembranch", + "fern-birds-nest-bacterial-soft-rot", + "fern-birds-nest-downy-mildew-generic", + "fern-birds-nest-viral-leaf-curl", + "fern-birds-nest-wood-rot-decay", + "philodendron-brasil-root-rot-aroidsoverwatering", + "philodendron-brasil-root-rot-pythiumphytophthora", + "philodendron-brasil-damping-off", + "philodendron-brasil-gray-mold-botrytis-blight", + "philodendron-brasil-mosaic-virus", + "philodendron-brasil-wilt-fusarium-or-verticillium", + "philodendron-brasil-root-knot-nematode", + "philodendron-brasil-canker-stembranch", + "philodendron-brasil-bacterial-soft-rot", + "philodendron-brasil-downy-mildew-generic", + "philodendron-brasil-viral-leaf-curl", + "philodendron-brasil-wood-rot-decay", + "philodendron-monstera-root-rot-aroidsoverwatering", + "philodendron-monstera-root-rot-pythiumphytophthora", + "philodendron-monstera-damping-off", + "philodendron-monstera-gray-mold-botrytis-blight", + "philodendron-monstera-mosaic-virus", + "philodendron-monstera-wilt-fusarium-or-verticillium", + "philodendron-monstera-root-knot-nematode", + "philodendron-monstera-canker-stembranch", + "philodendron-monstera-bacterial-soft-rot", + "philodendron-monstera-downy-mildew-generic", + "philodendron-monstera-viral-leaf-curl", + "philodendron-monstera-wood-rot-decay", + "pothos-marble-queen-root-rot-aroidsoverwatering", + "pothos-marble-queen-root-rot-pythiumphytophthora", + "pothos-marble-queen-damping-off", + "pothos-marble-queen-gray-mold-botrytis-blight", + "pothos-marble-queen-mosaic-virus", + "pothos-marble-queen-wilt-fusarium-or-verticillium", + "pothos-marble-queen-root-knot-nematode", + "pothos-marble-queen-canker-stembranch", + "pothos-marble-queen-bacterial-soft-rot", + "pothos-marble-queen-downy-mildew-generic", + "pothos-marble-queen-viral-leaf-curl", + "pothos-marble-queen-wood-rot-decay", + "peace-lily-sensation-root-rot-aroidsoverwatering", + "peace-lily-sensation-root-rot-pythiumphytophthora", + "peace-lily-sensation-damping-off", + "peace-lily-sensation-gray-mold-botrytis-blight", + "peace-lily-sensation-mosaic-virus", + "peace-lily-sensation-wilt-fusarium-or-verticillium", + "peace-lily-sensation-root-knot-nematode", + "peace-lily-sensation-canker-stembranch", + "peace-lily-sensation-bacterial-soft-rot", + "peace-lily-sensation-downy-mildew-generic", + "peace-lily-sensation-viral-leaf-curl", + "peace-lily-sensation-wood-rot-decay", + "phalaenopsis-orchid-root-rot-pythiumphytophthora", + "phalaenopsis-orchid-damping-off", + "phalaenopsis-orchid-gray-mold-botrytis-blight", + "phalaenopsis-orchid-mosaic-virus", + "phalaenopsis-orchid-wilt-fusarium-or-verticillium", + "phalaenopsis-orchid-root-knot-nematode", + "phalaenopsis-orchid-canker-stembranch", + "phalaenopsis-orchid-bacterial-soft-rot", + "phalaenopsis-orchid-downy-mildew-generic", + "phalaenopsis-orchid-viral-leaf-curl", + "phalaenopsis-orchid-wood-rot-decay", + "cattleya-orchid-root-rot-pythiumphytophthora", + "cattleya-orchid-damping-off", + "cattleya-orchid-gray-mold-botrytis-blight", + "cattleya-orchid-mosaic-virus", + "cattleya-orchid-wilt-fusarium-or-verticillium", + "cattleya-orchid-root-knot-nematode", + "cattleya-orchid-canker-stembranch", + "cattleya-orchid-bacterial-soft-rot", + "cattleya-orchid-downy-mildew-generic", + "cattleya-orchid-viral-leaf-curl", + "cattleya-orchid-wood-rot-decay", + "dendrobium-orchid-root-rot-pythiumphytophthora", + "dendrobium-orchid-damping-off", + "dendrobium-orchid-gray-mold-botrytis-blight", + "dendrobium-orchid-mosaic-virus", + "dendrobium-orchid-wilt-fusarium-or-verticillium", + "dendrobium-orchid-root-knot-nematode", + "dendrobium-orchid-canker-stembranch", + "dendrobium-orchid-bacterial-soft-rot", + "dendrobium-orchid-downy-mildew-generic", + "dendrobium-orchid-viral-leaf-curl", + "dendrobium-orchid-wood-rot-decay", + "oncidium-orchid-root-rot-pythiumphytophthora", + "oncidium-orchid-damping-off", + "oncidium-orchid-gray-mold-botrytis-blight", + "oncidium-orchid-mosaic-virus", + "oncidium-orchid-wilt-fusarium-or-verticillium", + "oncidium-orchid-root-knot-nematode", + "oncidium-orchid-canker-stembranch", + "oncidium-orchid-bacterial-soft-rot", + "oncidium-orchid-downy-mildew-generic", + "oncidium-orchid-viral-leaf-curl", + "oncidium-orchid-wood-rot-decay", + "begonia-root-rot-pythiumphytophthora", + "begonia-damping-off", + "begonia-gray-mold-botrytis-blight", + "begonia-mosaic-virus", + "begonia-wilt-fusarium-or-verticillium", + "begonia-root-knot-nematode", + "begonia-canker-stembranch", + "begonia-bacterial-soft-rot", + "begonia-downy-mildew-generic", + "begonia-viral-leaf-curl", + "begonia-wood-rot-decay", + "impatiens-root-rot-pythiumphytophthora", + "impatiens-damping-off", + "impatiens-gray-mold-botrytis-blight", + "impatiens-mosaic-virus", + "impatiens-wilt-fusarium-or-verticillium", + "impatiens-root-knot-nematode", + "impatiens-canker-stembranch", + "impatiens-bacterial-soft-rot", + "impatiens-downy-mildew-generic", + "impatiens-viral-leaf-curl", + "impatiens-wood-rot-decay", + "geranium-root-rot-pythiumphytophthora", + "geranium-damping-off", + "geranium-gray-mold-botrytis-blight", + "geranium-mosaic-virus", + "geranium-wilt-fusarium-or-verticillium", + "geranium-root-knot-nematode", + "geranium-canker-stembranch", + "geranium-bacterial-soft-rot", + "geranium-downy-mildew-generic", + "geranium-viral-leaf-curl", + "geranium-wood-rot-decay", + "cyclamen-root-rot-pythiumphytophthora", + "cyclamen-damping-off", + "cyclamen-gray-mold-botrytis-blight", + "cyclamen-mosaic-virus", + "cyclamen-wilt-fusarium-or-verticillium", + "cyclamen-root-knot-nematode", + "cyclamen-canker-stembranch", + "cyclamen-bacterial-soft-rot", + "cyclamen-downy-mildew-generic", + "cyclamen-viral-leaf-curl", + "cyclamen-wood-rot-decay", + "african-violet-root-rot-pythiumphytophthora", + "african-violet-damping-off", + "african-violet-gray-mold-botrytis-blight", + "african-violet-mosaic-virus", + "african-violet-wilt-fusarium-or-verticillium", + "african-violet-root-knot-nematode", + "african-violet-canker-stembranch", + "african-violet-bacterial-soft-rot", + "african-violet-downy-mildew-generic", + "african-violet-viral-leaf-curl", + "african-violet-wood-rot-decay", + "gloxinia-root-rot-pythiumphytophthora", + "gloxinia-damping-off", + "gloxinia-gray-mold-botrytis-blight", + "gloxinia-mosaic-virus", + "gloxinia-wilt-fusarium-or-verticillium", + "gloxinia-root-knot-nematode", + "gloxinia-canker-stembranch", + "gloxinia-bacterial-soft-rot", + "gloxinia-downy-mildew-generic", + "gloxinia-viral-leaf-curl", + "gloxinia-wood-rot-decay", + "cucumber-horned-downy-mildew-cucurbits", + "cucumber-horned-gummy-stem-blight", + "cucumber-horned-root-rot-pythiumphytophthora", + "cucumber-horned-damping-off", + "cucumber-horned-gray-mold-botrytis-blight", + "cucumber-horned-mosaic-virus", + "cucumber-horned-wilt-fusarium-or-verticillium", + "cucumber-horned-root-knot-nematode", + "cucumber-horned-canker-stembranch", + "cucumber-horned-bacterial-soft-rot", + "cucumber-horned-downy-mildew-generic", + "cucumber-horned-viral-leaf-curl", + "cucumber-horned-wood-rot-decay", + "sweet-potato-leaf-root-rot-pythiumphytophthora", + "sweet-potato-leaf-damping-off", + "sweet-potato-leaf-gray-mold-botrytis-blight", + "sweet-potato-leaf-mosaic-virus", + "sweet-potato-leaf-wilt-fusarium-or-verticillium", + "sweet-potato-leaf-root-knot-nematode", + "sweet-potato-leaf-canker-stembranch", + "sweet-potato-leaf-bacterial-soft-rot", + "sweet-potato-leaf-downy-mildew-generic", + "sweet-potato-leaf-viral-leaf-curl", + "sweet-potato-leaf-wood-rot-decay", + "ivy-english-root-rot-pythiumphytophthora", + "ivy-english-damping-off", + "ivy-english-gray-mold-botrytis-blight", + "ivy-english-mosaic-virus", + "ivy-english-wilt-fusarium-or-verticillium", + "ivy-english-root-knot-nematode", + "ivy-english-canker-stembranch", + "ivy-english-bacterial-soft-rot", + "ivy-english-downy-mildew-generic", + "ivy-english-viral-leaf-curl", + "ivy-english-wood-rot-decay", + "ivy-swedish-downy-mildew-lamiaceaebasil", + "ivy-swedish-basil-fusarium-wilt", + "ivy-swedish-root-rot-pythiumphytophthora", + "ivy-swedish-damping-off", + "ivy-swedish-gray-mold-botrytis-blight", + "ivy-swedish-mosaic-virus", + "ivy-swedish-wilt-fusarium-or-verticillium", + "ivy-swedish-root-knot-nematode", + "ivy-swedish-canker-stembranch", + "ivy-swedish-bacterial-soft-rot", + "ivy-swedish-downy-mildew-generic", + "ivy-swedish-viral-leaf-curl", + "ivy-swedish-wood-rot-decay", + "banana-dwarf-root-rot-pythiumphytophthora", + "banana-dwarf-damping-off", + "banana-dwarf-gray-mold-botrytis-blight", + "banana-dwarf-mosaic-virus", + "banana-dwarf-wilt-fusarium-or-verticillium", + "banana-dwarf-root-knot-nematode", + "banana-dwarf-canker-stembranch", + "banana-dwarf-bacterial-soft-rot", + "banana-dwarf-downy-mildew-generic", + "banana-dwarf-viral-leaf-curl", + "banana-dwarf-wood-rot-decay", + "mimosa-white-mold-sclerotinia-rot", + "mimosa-charcoal-rot", + "mimosa-root-rot-pythiumphytophthora", + "mimosa-damping-off", + "mimosa-gray-mold-botrytis-blight", + "mimosa-mosaic-virus", + "mimosa-wilt-fusarium-or-verticillium", + "mimosa-root-knot-nematode", + "mimosa-canker-stembranch", + "mimosa-bacterial-soft-rot", + "mimosa-downy-mildew-generic", + "mimosa-viral-leaf-curl", + "mimosa-wood-rot-decay", + "kentucky-coffee-white-mold-sclerotinia-rot", + "kentucky-coffee-charcoal-rot", + "kentucky-coffee-root-rot-pythiumphytophthora", + "kentucky-coffee-damping-off", + "kentucky-coffee-gray-mold-botrytis-blight", + "kentucky-coffee-mosaic-virus", + "kentucky-coffee-wilt-fusarium-or-verticillium", + "kentucky-coffee-root-knot-nematode", + "kentucky-coffee-canker-stembranch", + "kentucky-coffee-bacterial-soft-rot", + "kentucky-coffee-downy-mildew-generic", + "kentucky-coffee-viral-leaf-curl", + "kentucky-coffee-wood-rot-decay", + "redbud-white-mold-sclerotinia-rot", + "redbud-charcoal-rot", + "redbud-root-rot-pythiumphytophthora", + "redbud-damping-off", + "redbud-gray-mold-botrytis-blight", + "redbud-mosaic-virus", + "redbud-wilt-fusarium-or-verticillium", + "redbud-root-knot-nematode", + "redbud-canker-stembranch", + "redbud-bacterial-soft-rot", + "redbud-downy-mildew-generic", + "redbud-viral-leaf-curl", + "redbud-wood-rot-decay", + "tulip-tree-root-rot-pythiumphytophthora", + "tulip-tree-damping-off", + "tulip-tree-gray-mold-botrytis-blight", + "tulip-tree-mosaic-virus", + "tulip-tree-wilt-fusarium-or-verticillium", + "tulip-tree-root-knot-nematode", + "tulip-tree-canker-stembranch", + "tulip-tree-bacterial-soft-rot", + "tulip-tree-downy-mildew-generic", + "tulip-tree-viral-leaf-curl", + "tulip-tree-wood-rot-decay", + "sweetgum-root-rot-pythiumphytophthora", + "sweetgum-damping-off", + "sweetgum-gray-mold-botrytis-blight", + "sweetgum-mosaic-virus", + "sweetgum-wilt-fusarium-or-verticillium", + "sweetgum-root-knot-nematode", + "sweetgum-canker-stembranch", + "sweetgum-bacterial-soft-rot", + "sweetgum-downy-mildew-generic", + "sweetgum-viral-leaf-curl", + "sweetgum-wood-rot-decay", + "crabapple-brown-rot-stone-fruit", + "crabapple-root-rot-pythiumphytophthora", + "crabapple-damping-off", + "crabapple-gray-mold-botrytis-blight", + "crabapple-mosaic-virus", + "crabapple-wilt-fusarium-or-verticillium", + "crabapple-root-knot-nematode", + "crabapple-canker-stembranch", + "crabapple-bacterial-soft-rot", + "crabapple-downy-mildew-generic", + "crabapple-viral-leaf-curl", + "crabapple-wood-rot-decay", + "serviceberry-brown-rot-stone-fruit", + "serviceberry-root-rot-pythiumphytophthora", + "serviceberry-damping-off", + "serviceberry-gray-mold-botrytis-blight", + "serviceberry-mosaic-virus", + "serviceberry-wilt-fusarium-or-verticillium", + "serviceberry-root-knot-nematode", + "serviceberry-canker-stembranch", + "serviceberry-bacterial-soft-rot", + "serviceberry-downy-mildew-generic", + "serviceberry-viral-leaf-curl", + "serviceberry-wood-rot-decay", + "chokecherry-brown-rot-stone-fruit", + "chokecherry-root-rot-pythiumphytophthora", + "chokecherry-damping-off", + "chokecherry-gray-mold-botrytis-blight", + "chokecherry-mosaic-virus", + "chokecherry-wilt-fusarium-or-verticillium", + "chokecherry-root-knot-nematode", + "chokecherry-canker-stembranch", + "chokecherry-bacterial-soft-rot", + "chokecherry-downy-mildew-generic", + "chokecherry-viral-leaf-curl", + "chokecherry-wood-rot-decay", + "buckeye-root-rot-pythiumphytophthora", + "buckeye-damping-off", + "buckeye-gray-mold-botrytis-blight", + "buckeye-mosaic-virus", + "buckeye-wilt-fusarium-or-verticillium", + "buckeye-root-knot-nematode", + "buckeye-canker-stembranch", + "buckeye-bacterial-soft-rot", + "buckeye-downy-mildew-generic", + "buckeye-viral-leaf-curl", + "buckeye-wood-rot-decay", + "linden-root-rot-pythiumphytophthora", + "linden-damping-off", + "linden-gray-mold-botrytis-blight", + "linden-mosaic-virus", + "linden-wilt-fusarium-or-verticillium", + "linden-root-knot-nematode", + "linden-canker-stembranch", + "linden-bacterial-soft-rot", + "linden-downy-mildew-generic", + "linden-viral-leaf-curl", + "linden-wood-rot-decay", + "ginkgo-root-rot-pythiumphytophthora", + "ginkgo-damping-off", + "ginkgo-gray-mold-botrytis-blight", + "ginkgo-mosaic-virus", + "ginkgo-wilt-fusarium-or-verticillium", + "ginkgo-root-knot-nematode", + "ginkgo-canker-stembranch", + "ginkgo-bacterial-soft-rot", + "ginkgo-downy-mildew-generic", + "ginkgo-viral-leaf-curl", + "ginkgo-wood-rot-decay", + "ficus-microcarpa-root-rot-pythiumphytophthora", + "ficus-microcarpa-damping-off", + "ficus-microcarpa-gray-mold-botrytis-blight", + "ficus-microcarpa-mosaic-virus", + "ficus-microcarpa-wilt-fusarium-or-verticillium", + "ficus-microcarpa-root-knot-nematode", + "ficus-microcarpa-canker-stembranch", + "ficus-microcarpa-bacterial-soft-rot", + "ficus-microcarpa-downy-mildew-generic", + "ficus-microcarpa-viral-leaf-curl", + "ficus-microcarpa-wood-rot-decay", + "schefflera-root-rot-pythiumphytophthora", + "schefflera-damping-off", + "schefflera-gray-mold-botrytis-blight", + "schefflera-mosaic-virus", + "schefflera-wilt-fusarium-or-verticillium", + "schefflera-root-knot-nematode", + "schefflera-canker-stembranch", + "schefflera-bacterial-soft-rot", + "schefflera-downy-mildew-generic", + "schefflera-viral-leaf-curl", + "schefflera-wood-rot-decay", + "maranta-root-rot-pythiumphytophthora", + "maranta-damping-off", + "maranta-gray-mold-botrytis-blight", + "maranta-mosaic-virus", + "maranta-wilt-fusarium-or-verticillium", + "maranta-root-knot-nematode", + "maranta-canker-stembranch", + "maranta-bacterial-soft-rot", + "maranta-downy-mildew-generic", + "maranta-viral-leaf-curl", + "maranta-wood-rot-decay", + "stromanthe-root-rot-pythiumphytophthora", + "stromanthe-damping-off", + "stromanthe-gray-mold-botrytis-blight", + "stromanthe-mosaic-virus", + "stromanthe-wilt-fusarium-or-verticillium", + "stromanthe-root-knot-nematode", + "stromanthe-canker-stembranch", + "stromanthe-bacterial-soft-rot", + "stromanthe-downy-mildew-generic", + "stromanthe-viral-leaf-curl", + "stromanthe-wood-rot-decay", + "bok-choy-shanghai-clubroot", + "bok-choy-shanghai-black-rot-brassicas", + "bok-choy-shanghai-root-rot-pythiumphytophthora", + "bok-choy-shanghai-damping-off", + "bok-choy-shanghai-gray-mold-botrytis-blight", + "bok-choy-shanghai-mosaic-virus", + "bok-choy-shanghai-wilt-fusarium-or-verticillium", + "bok-choy-shanghai-root-knot-nematode", + "bok-choy-shanghai-canker-stembranch", + "bok-choy-shanghai-bacterial-soft-rot", + "bok-choy-shanghai-downy-mildew-generic", + "bok-choy-shanghai-viral-leaf-curl", + "bok-choy-shanghai-wood-rot-decay", + "tatsoi-clubroot", + "tatsoi-black-rot-brassicas", + "tatsoi-root-rot-pythiumphytophthora", + "tatsoi-damping-off", + "tatsoi-gray-mold-botrytis-blight", + "tatsoi-mosaic-virus", + "tatsoi-wilt-fusarium-or-verticillium", + "tatsoi-root-knot-nematode", + "tatsoi-canker-stembranch", + "tatsoi-bacterial-soft-rot", + "tatsoi-downy-mildew-generic", + "tatsoi-viral-leaf-curl", + "tatsoi-wood-rot-decay", + "mizuna-clubroot", + "mizuna-black-rot-brassicas", + "mizuna-root-rot-pythiumphytophthora", + "mizuna-damping-off", + "mizuna-gray-mold-botrytis-blight", + "mizuna-mosaic-virus", + "mizuna-wilt-fusarium-or-verticillium", + "mizuna-root-knot-nematode", + "mizuna-canker-stembranch", + "mizuna-bacterial-soft-rot", + "mizuna-downy-mildew-generic", + "mizuna-viral-leaf-curl", + "mizuna-wood-rot-decay", + "kohlrabi-clubroot", + "kohlrabi-black-rot-brassicas", + "kohlrabi-root-rot-pythiumphytophthora", + "kohlrabi-damping-off", + "kohlrabi-gray-mold-botrytis-blight", + "kohlrabi-mosaic-virus", + "kohlrabi-wilt-fusarium-or-verticillium", + "kohlrabi-root-knot-nematode", + "kohlrabi-canker-stembranch", + "kohlrabi-bacterial-soft-rot", + "kohlrabi-downy-mildew-generic", + "kohlrabi-viral-leaf-curl", + "kohlrabi-wood-rot-decay", + "rapini-clubroot", + "rapini-black-rot-brassicas", + "rapini-root-rot-pythiumphytophthora", + "rapini-damping-off", + "rapini-gray-mold-botrytis-blight", + "rapini-mosaic-virus", + "rapini-wilt-fusarium-or-verticillium", + "rapini-root-knot-nematode", + "rapini-canker-stembranch", + "rapini-bacterial-soft-rot", + "rapini-downy-mildew-generic", + "rapini-viral-leaf-curl", + "rapini-wood-rot-decay", + "jicama-white-mold-sclerotinia-rot", + "jicama-charcoal-rot", + "jicama-root-rot-pythiumphytophthora", + "jicama-damping-off", + "jicama-gray-mold-botrytis-blight", + "jicama-mosaic-virus", + "jicama-wilt-fusarium-or-verticillium", + "jicama-root-knot-nematode", + "jicama-canker-stembranch", + "jicama-bacterial-soft-rot", + "jicama-downy-mildew-generic", + "jicama-viral-leaf-curl", + "jicama-wood-rot-decay", + "adzuki-bean-white-mold-sclerotinia-rot", + "adzuki-bean-charcoal-rot", + "adzuki-bean-root-rot-pythiumphytophthora", + "adzuki-bean-damping-off", + "adzuki-bean-gray-mold-botrytis-blight", + "adzuki-bean-mosaic-virus", + "adzuki-bean-wilt-fusarium-or-verticillium", + "adzuki-bean-root-knot-nematode", + "adzuki-bean-canker-stembranch", + "adzuki-bean-bacterial-soft-rot", + "adzuki-bean-downy-mildew-generic", + "adzuki-bean-viral-leaf-curl", + "adzuki-bean-wood-rot-decay", + "mung-bean-white-mold-sclerotinia-rot", + "mung-bean-charcoal-rot", + "mung-bean-root-rot-pythiumphytophthora", + "mung-bean-damping-off", + "mung-bean-gray-mold-botrytis-blight", + "mung-bean-mosaic-virus", + "mung-bean-wilt-fusarium-or-verticillium", + "mung-bean-root-knot-nematode", + "mung-bean-canker-stembranch", + "mung-bean-bacterial-soft-rot", + "mung-bean-downy-mildew-generic", + "mung-bean-viral-leaf-curl", + "mung-bean-wood-rot-decay", + "garbanzo-white-mold-sclerotinia-rot", + "garbanzo-charcoal-rot", + "garbanzo-root-rot-pythiumphytophthora", + "garbanzo-damping-off", + "garbanzo-gray-mold-botrytis-blight", + "garbanzo-mosaic-virus", + "garbanzo-wilt-fusarium-or-verticillium", + "garbanzo-root-knot-nematode", + "garbanzo-canker-stembranch", + "garbanzo-bacterial-soft-rot", + "garbanzo-downy-mildew-generic", + "garbanzo-viral-leaf-curl", + "garbanzo-wood-rot-decay", + "wiki-acrophialophora-wilt", + "wiki-aerial-blight", + "wiki-aerial-stem-rot", + "wiki-almond-brown-line-and-decline", + "wiki-almond-hull-rot", + "wiki-alternaria-blight", + "wiki-alternaria-canker", + "wiki-alternaria-late-blight", + "wiki-alternaria-stem-blight", + "wiki-annosum-root-rot", + "wiki-anthracnose-leaf-blight", + "wiki-anthracnose-stalk-rot", + "wiki-anthracnose-top-dieback", + "wiki-aphid-blight", + "wiki-apple-bitter-rot", + "wiki-apple-black-rot-canker", + "wiki-apple-calyx-end-rot", + "wiki-apple-collar-rot", + "wiki-apple-crown-rot", + "wiki-apple-internal-bark-necrosis", + "wiki-apple-monilia-leaf-blight", + "wiki-apple-perennial-canker", + "wiki-apple-ring-rot", + "wiki-apple-southern-blight", + "wiki-apple-spy-decline", + "wiki-apple-white-rot", + "wiki-armillaria-corn-rot", + "wiki-armillaria-crown-and-root-rot", + "wiki-ascochyta-foot-rot", + "wiki-ascochyta-leaf-blight", + "wiki-ascospora-dieback", + "wiki-asparagus-decline-virus", + "wiki-aspergillus-ear-rot", + "wiki-aspergillus-fruit-rot", + "wiki-autogenic-necrosis", + "wiki-bacterial-black-rot", + "wiki-bacterial-blight-of-carrot", + "wiki-bacterial-blight-of-pea", + "wiki-bacterial-hyperplastic-canker", + "wiki-bacterial-necrosis", + "wiki-bacterial-panicle-blight", + "wiki-bacterial-pod-rot", + "wiki-bacterial-slow-wilt", + "wiki-bacterial-soft-rot-of-carrot", + "wiki-bacterial-soft-rot-of-onion", + "wiki-bacterial-stem-gall", + "wiki-bacterial-stem-rot", + "wiki-bacterial-stripe-blight", + "wiki-bacterial-vascular-necrosis-and-rot", + "wiki-band-canker", + "wiki-bark-canker", + "wiki-barker-canker", + "wiki-barley-common-root-rot", + "wiki-basal-stem-and-crown-rot", + "wiki-basal-stem-blight", + "wiki-bitter-rot-of-apple", + "wiki-black-root-and-stem-rot", + "wiki-black-root-rot-of-carrot", + "wiki-black-rot-of-apple", + "wiki-black-rot-of-cabbage", + "wiki-black-stem-rot", + "wiki-black-streak-root-rot", + "wiki-bleaching-necrosis", + "wiki-bleeding-canker", + "wiki-blossom-and-shoot-blight", + "wiki-boll-blight", + "wiki-boll-rot", + "wiki-boll-rot-complex", + "wiki-botryodiplodia-blight", + "wiki-botryodiplodia-canker", + "wiki-botryosphaeria-cane-canker", + "wiki-botryosphaeria-canker", + "wiki-botryosphaeria-dieback", + "wiki-botryosphaeria-stem-canker", + "wiki-botryosphaeria-stem-rot", + "wiki-botrytis-bunch-rot", + "wiki-botrytis-fruit-rot", + "wiki-botrytis-hard-rot", + "wiki-botrytis-head-rot", + "wiki-botrytis-leaf-and-petal-blight", + "wiki-botrytis-petal-blight", + "wiki-boysenberry-decline", + "wiki-bract-and-flower-blight", + "wiki-bract-necrosis", + "wiki-brand-canker", + "wiki-brown-canker", + "wiki-brown-girdling-root-rot", + "wiki-brown-rot-blossom-and-twig-blight", + "wiki-brown-rot-blossom-blight", + "wiki-brown-stem-blight", + "wiki-buckeye-rot", + "wiki-bud-and-twig-blight", + "wiki-bulb-rot", + "wiki-bunch-rot", + "wiki-calyx-end-rot-of-apple", + "wiki-calyx-rot", + "wiki-camarosporium-shoot-and-panicle-blight", + "wiki-cane-blight", + "wiki-cane-gall", + "wiki-canker-rots", + "wiki-cephalosporium-root-rot", + "wiki-ceratocystis-fruit-rot", + "wiki-ceratosystis-canker", + "wiki-chalara-root-rot", + "wiki-cherelle-wilt", + "wiki-cherry-wilt", + "wiki-chestnut-blight", + "wiki-choanephora-rot", + "wiki-christmas-cactus-necrosis", + "wiki-citrus-gummosis", + "wiki-cladosporium-blight", + "wiki-cladosporium-ear-rot", + "wiki-cladosporium-leaf-blight", + "wiki-cladosporium-rot", + "wiki-cladosporium-stem-canker", + "wiki-cocoa-necrosis", + "wiki-coconut-lethal-yellowing", + "wiki-coffee-wilt", + "wiki-coffee-wilt-disease", + "wiki-collar-rot-of-apple", + "wiki-colletotrichum-blight", + "wiki-colletotrichum-stem-canker", + "wiki-colluvial-blight", + "wiki-common-bacterial-blight", + "wiki-common-bean-bacterial-blight", + "wiki-common-root-rot", + "wiki-coniothyrium-canker", + "wiki-coriander-stem-gall", + "wiki-corm-dry-rot", + "wiki-corn-bacterial-stalk-rot", + "wiki-corn-ear-rot", + "wiki-corn-leaf-blight", + "wiki-corn-lethal-necrosis", + "wiki-corn-northern-leaf-blight", + "wiki-corn-southern-leaf-blight", + "wiki-corn-stewart-wilt", + "wiki-corticium-root-rot", + "wiki-corynespora-blight", + "wiki-coryneum-blight", + "wiki-cotton-anthracnose-boll-rot", + "wiki-cotton-boll-rot", + "wiki-cotton-root-rot", + "wiki-cottony-stem-rot", + "wiki-cowpea-severe-mosaic", + "wiki-cranberry-fruit-rot", + "wiki-cranberry-twig-blight", + "wiki-crown-and-cane-gall", + "wiki-crown-and-stem-rot", + "wiki-crown-gall-of-apple", + "wiki-crown-root-rot", + "wiki-crown-rot-of-apple", + "wiki-cucumber-bacterial-wilt", + "wiki-cucumber-necrosis", + "wiki-curvularia-leaf-blight", + "wiki-cushion-gall", + "wiki-cutting-rot", + "wiki-cylindrocarpon-canker", + "wiki-cylindrocarpon-root-and-crown-rot", + "wiki-cylindrocladiella-root-rot", + "wiki-cylindrocladium-blight", + "wiki-cylindrocladium-root-rot", + "wiki-cylindrosporium-blight", + "wiki-cytosporina-canker", + "wiki-daffodil-bulb-rot", + "wiki-dahlia-wilt", + "wiki-datura-necrosis", + "wiki-delphinium-crown-rot", + "wiki-dieback-and-canker", + "wiki-diplodia-boll-rot", + "wiki-diplodia-collar-rot", + "wiki-diplodia-ear-rot", + "wiki-diplodia-fruit-rot", + "wiki-diplodia-root-and-stem-rot", + "wiki-diplodia-stem-canker", + "wiki-diplodia-stem-end-rot", + "wiki-dogwood-canker", + "wiki-dothiorella-blight", + "wiki-dothiorella-canker", + "wiki-dwarf-cavendish-tip-rot", + "wiki-ear-blight", + "wiki-ear-rot", + "wiki-eastern-filbert-blight", + "wiki-eggplant-verticillium-wilt", + "wiki-elm-phloem-necrosis", + "wiki-endothia-canker", + "wiki-english-walnut-blight", + "wiki-eucalyptus-canker", + "wiki-eucalyptus-dieback", + "wiki-eucalyptus-shoot-blight", + "wiki-european-apple-canker", + "wiki-european-canker-of-apple", + "wiki-european-decline", + "wiki-european-filbert-blight", + "wiki-european-larch-canker", + "wiki-eutypa-canker-of-grape", + "wiki-eutypella-canker", + "wiki-exserohilum-leaf-blight", + "wiki-finger-tip-rot", + "wiki-flower-blight", + "wiki-foamy-canker", + "wiki-foliage-blight", + "wiki-fruit-rots", + "wiki-fruitlet-core-rot", + "wiki-fungal-root-rot", + "wiki-fusarium-bud-rot", + "wiki-fusarium-canker", + "wiki-fusarium-crown-rot", + "wiki-fusarium-cutting-rot", + "wiki-fusarium-ear-rot", + "wiki-fusarium-foot-rot", + "wiki-fusarium-head-blight", + "wiki-fusarium-kernel-rot", + "wiki-fusarium-leaf-blight", + "wiki-fusarium-root-and-crown-rot", + "wiki-fusarium-rot", + "wiki-fusarium-seedling-rot", + "wiki-fusarium-stem-canker", + "wiki-fusarium-stem-rot", + "wiki-fusarium-yellows-and-root-rot", + "wiki-fuscous-blight", + "wiki-fusicoccum-canker", + "wiki-ganoderma-root-rot", + "wiki-gibberella-ear-rot", + "wiki-gibberella-stalk-rot", + "wiki-glomerella-canker", + "wiki-glomerella-stem-rot", + "wiki-gnomonia-cane-canker", + "wiki-gray-blight", + "wiki-gum-canker", + "wiki-head-blight", + "wiki-head-rot", + "wiki-hemp-canker", + "wiki-horse-hair-blight", + "wiki-hull-rot", + "wiki-hymenochaete-canker", + "wiki-hypoxylon-canker", + "wiki-inflorescence-rot", + "wiki-internal-bark-necrosis-of-apple", + "wiki-javanese-vascular-wilt", + "wiki-kernel-blight", + "wiki-kernel-rot", + "wiki-lasiodiplodia-leaf-and-stem-rot", + "wiki-lasiodiplodia-pod-rot", + "wiki-lasiodiplodia-vine-decline", + "wiki-leader-dieback", + "wiki-leaf-and-flower-gall", + "wiki-leaf-dieback", + "wiki-leaf-necrosis", + "wiki-leafy-gall", + "wiki-lenticel-rot", + "wiki-leptosphaeria-blight", + "wiki-lethal-necrosis", + "wiki-leucostoma-canker", + "wiki-macrophoma-pod-rot", + "wiki-macrophoma-stem-canker", + "wiki-macrophomina-stem-canker", + "wiki-main-stalk-rot", + "wiki-marasmiellus-rot", + "wiki-marginal-necrosis", + "wiki-massaria-canker", + "wiki-mealybug-wilt", + "wiki-midge-blight", + "wiki-monilia-leaf-blight-of-apple", + "wiki-monosporascus-root-rot", + "wiki-mycosphaerella-blight", + "wiki-neck-rot", + "wiki-nematode-root-rot", + "wiki-neocosmospora-root-rot", + "wiki-nigrospora-ear-rot", + "wiki-noble-rot", + "wiki-north-american-raspberry-decline", + "wiki-nursery-blight", + "wiki-omphalia-root-rot", + "wiki-ophiobolus-stem-canker", + "wiki-ovulinia-petal-blight", + "wiki-ozonium-collar-rot", + "wiki-ozonium-wilt", + "wiki-panicle-and-shoot-blight", + "wiki-panicle-blight", + "wiki-pea-stem-rot", + "wiki-peanut-bud-necrosis", + "wiki-pear-leaf-blight", + "wiki-peduncle-rot", + "wiki-penicillium-rot", + "wiki-perennial-canker-of-apple", + "wiki-petal-blight", + "wiki-phialophora-wilt", + "wiki-phoma-boll-rot", + "wiki-phoma-canker", + "wiki-phoma-root-rot", + "wiki-phoma-stalk-rot", + "wiki-phoma-stem-canker", + "wiki-phoma-wilt", + "wiki-phomopsis-brown-stem-rot", + "wiki-phomopsis-cane-canker", + "wiki-phomopsis-canker", + "wiki-phomopsis-dieback", + "wiki-phomopsis-leaf-blight", + "wiki-phomopsis-rot", + "wiki-phomopsis-seed-rot", + "wiki-phomopsis-shoot-blight", + "wiki-phomopsis-stem-canker", + "wiki-physoderma-stalk-rot", + "wiki-phytophthora-canker", + "wiki-phytophthora-collar-rot", + "wiki-phytophthora-dieback", + "wiki-phytophthora-gummosis", + "wiki-phytophthora-root-and-crown-rot", + "wiki-phytophthora-shuck-and-kernel-rot", + "wiki-phytophthora-stem-canker", + "wiki-phytophthora-trunk-and-bark-canker", + "wiki-phytophthora-wet-rot", + "wiki-phytophthora-wilt", + "wiki-pink-rot-of-inflorescence", + "wiki-pod-rot", + "wiki-post-harvest-soft-rot", + "wiki-post-harvest-root-rot", + "wiki-pseudomonas-blight", + "wiki-pseudostem-heart-rot", + "wiki-pythium-blight", + "wiki-pythium-brown-rot", + "wiki-pythium-fruit-rot", + "wiki-pythium-pod-rot", + "wiki-pythium-root-and-seedling-rot", + "wiki-red-kernel-rot", + "wiki-red-rot-of-leaf-sheath", + "wiki-rhizoctonia-blight", + "wiki-rhizoctonia-boll-rot", + "wiki-rhizoctonia-ear-rot", + "wiki-rhizoctonia-foliar-blight", + "wiki-rhizoctonia-head-rot", + "wiki-rhizoctonia-limb-rot", + "wiki-rhizoctonia-pod-rot", + "wiki-rhizoctonia-root-and-crown-rot", + "wiki-rhizoctonia-sheath-blight", + "wiki-rhizoctonia-stem-canker", + "wiki-rhizoctonia-stem-rot", + "wiki-rhizome-rot", + "wiki-rhizopus-ear-rot", + "wiki-rhizopus-root-rot", + "wiki-rice-gall-dwarf", + "wiki-rice-necrosis-mosaic", + "wiki-rice-wilted-stunt", + "wiki-rigidopurus-root-rot", + "wiki-rind-necrosis", + "wiki-ring-rot-of-apple", + "wiki-root-and-rhizome-rot", + "wiki-root-and-stem-rot", + "wiki-root-gall-smut", + "wiki-root-rot-of-apple", + "wiki-root-tip-rot", + "wiki-rose-canker", + "wiki-rosy-canker", + "wiki-sapwood-rot", + "wiki-schizoxylon-canker", + "wiki-sclerotial-rot", + "wiki-sclerotinia-boll-rot", + "wiki-sclerotinia-crown-and-root-rot", + "wiki-sclerotinia-crown-rot", + "wiki-sclerotinia-flower-rot", + "wiki-sclerotinia-fruit-rot", + "wiki-sclerotinia-head-rot", + "wiki-sclerotinia-shoot-blight", + "wiki-sclerotinia-stalk-rot", + "wiki-sclerotinia-wilt", + "wiki-sclerotium-ear-rot", + "wiki-sclerotium-root-rot", + "wiki-seed-gall-nematode", + "wiki-seed-rot", + "wiki-seedling-or-seed-rot", + "wiki-seedling-rot", + "wiki-shoot-blight", + "wiki-shoot-dieback", + "wiki-shuck-decline", + "wiki-southern-blight-of-apple", + "wiki-southern-corn-leaf-blight", + "wiki-southern-stem-rot", + "wiki-southern-wilt", + "wiki-spur-blight", + "wiki-spy-decline", + "wiki-spy-decline-of-apple", + "wiki-stalk-rot", + "wiki-stamen-blight", + "wiki-stem-and-stolon-canker", + "wiki-stem-and-tuber-rot", + "wiki-stem-blight", + "wiki-stem-end-blight", + "wiki-stem-mold-and-rot", + "wiki-stemphylium-blight", + "wiki-stewart-s-wilt", + "wiki-storage-rot-of-apple", + "wiki-strawberry-root-rot", + "wiki-sweetgum-blight", + "wiki-sydowiella-cane-canker", + "wiki-synchytrium-brown-gall", + "wiki-synchytrium-orange-gall", + "wiki-syringae-seedling-blight", + "wiki-terminal-bud-rot", + "wiki-terminal-shoot-necrosis", + "wiki-texas-root-rot", + "wiki-thielaviopsis-root-rot", + "wiki-thyrostroma-canker", + "wiki-top-rot", + "wiki-trachysphaera-finger-rot", + "wiki-trachysphaera-pot-rot", + "wiki-trichoderma-ear-rot", + "wiki-trichoderma-foot-rot", + "wiki-trichoderma-stalk-rot", + "wiki-tropical-rot", + "wiki-trunk-canker", + "wiki-tuber-rot", + "wiki-tubercularia-canker", + "wiki-twig-dieback", + "wiki-valsa-canker-of-apple", + "wiki-vascular-streak-dieback", + "wiki-vascular-wilt", + "wiki-vein-necrosis", + "wiki-verticillium-root-and-stem-rot", + "wiki-verticillium-tip-rot", + "wiki-victoria-blight", + "wiki-walnut-blight", + "wiki-wet-leaf-rot", + "wiki-wet-root-rot", + "wiki-white-blight", + "wiki-white-ear-rot", + "wiki-white-mold-stem-rot", + "wiki-white-rot-of-apple", + "wiki-white-stem-blight", + "wiki-winter-crown-rot", + "wiki-winter-rot", + "wiki-xanthomonas-wilt", + "wiki-y-center-rot", + "wiki-yellow-wilt", + "wiki-zonate-canker", + "tomato-alternaria-stem-canker", + "tomato-anthracnose", + "tomato-black-mold-rot", + "tomato-black-root-rot", + "tomato-black-shoulder", + "tomato-buckeye-rot-of-tomato", + "tomato-cercospora-leaf-mold", + "tomato-charcoal-rot", + "tomato-corky-root-rot", + "tomato-didymella-stem-rot", + "tomato-early-blight", + "tomato-fusarium-crown-and-root-rot", + "tomato-fusarium-wilt", + "tomato-gray-leaf-spot", + "tomato-gray-mold", + "tomato-late-blight", + "tomato-leaf-mold", + "tomato-phoma-rot", + "tomato-pythium-damping-off-and-fruit-rot", + "tomato-rhizoctonia-damping-off-and-fruit-rot", + "tomato-rhizopus-rot", + "tomato-septoria-leaf-spot", + "tomato-sour-rot", + "tomato-southern-blight", + "tomato-target-spot", + "tomato-verticillium-wilt", + "tomato-white-mold", + "potato-black-dot", + "potato-brown-spot-and-black-pit", + "potato-cercospora-leaf-blotch", + "potato-charcoal-rot", + "potato-choanephora-blight", + "potato-deforming-rust", + "potato-early-blight", + "potato-fusarium-dry-rot", + "potato-fusarium-wilt", + "potato-gray-mold", + "potato-phoma-leaf-spot", + "potato-powdery-mildew", + "potato-rhizoctonia-canker-and-black-scurf", + "potato-rosellinia-black-rot", + "potato-septoria-leaf-spot", + "potato-silver-scurf", + "potato-skin-spot", + "potato-stem-rot-southern-blight", + "potato-thecaphora-smut", + "potato-ulocladium-blight", + "potato-verticillium-wilt", + "potato-white-mold", + "potato-", + "apple-alternaria-blotch", + "apple-alternaria-rot", + "apple-american-brown-rot", + "apple-anthracnose-canker-and-bulls-eye-rot", + "apple-apple-scab", + "apple-apple-ring-rot-and-canker", + "apple-armillaria-root-rot-shoestring-root-rot", + "apple-bitter-rot", + "apple-black-pox", + "apple-black-root-rot", + "apple-black-rot-frogeye-leafspot-and-canker", + "apple-blister-canker-nailhead-canker", + "apple-blue-mold", + "apple-brooks-fruit-spot", + "apple-brown-rot-blossom-blight-and-spur-infection", + "apple-calyx-end-rot", + "apple-clitocybe-root-rot", + "apple-diaporthe-canker", + "apple-diplodia-canker", + "apple-european-brown-rot", + "apple-fisheye-rot", + "apple-flyspeck", + "apple-fruit-blotch-leaf-spot-and-twig-canker", + "apple-glomerella-leaf-spot", + "apple-gray-mold-rot-dry-eye-rot-blossom-end-rot", + "apple-leptosphaeria-canker-and-fruit-rot", + "apple-leucostoma-canker-and-dieback", + "apple-marssonina-blotch", + "apple-moldy-core-and-core-rot", + "apple-monilia-leaf-blight", + "apple-monochaetia-twig-canker", + "apple-mucor-rot", + "apple-nectria-canker", + "apple-nectria-twig-blight-coral-spot", + "apple-peniophora-root-canker", + "apple-perennial-canker", + "apple-phomopsis-canker-fruit-decay-and-rough-bark" + ], + "currentKeyIndex": 0, + "callsThisKey": 1068, + "totalFound": 0 +} \ No newline at end of file diff --git a/apps/web/scripts/.ddg-progress.json b/apps/web/scripts/.ddg-progress.json new file mode 100644 index 0000000..28ade8f --- /dev/null +++ b/apps/web/scripts/.ddg-progress.json @@ -0,0 +1,655 @@ +{ + "processedIds": [ + "cucumber-horned-phytophthora-blight-cucurbits", + "crabapple-fire-blight", + "serviceberry-fire-blight", + "chokecherry-fire-blight", + "soybean-peanut-mottle", + "sweet-potato-little-leaf-proliferation-disease", + "sweet-potato-internal-cork", + "winter-squash-viral-leaf-curl", + "acorn-squash-viral-leaf-curl", + "butternut-squash-viral-leaf-curl", + "monstera-viral-leaf-curl", + "monstera-wood-rot-decay", + "pothos-downy-mildew-generic", + "pothos-viral-leaf-curl", + "pothos-wood-rot-decay", + "peace-lily-bacterial-soft-rot", + "peace-lily-downy-mildew-generic", + "peace-lily-viral-leaf-curl", + "peace-lily-wood-rot-decay", + "philodendron-bacterial-soft-rot", + "philodendron-downy-mildew-generic", + "philodendron-viral-leaf-curl", + "philodendron-wood-rot-decay", + "anthurium-bacterial-soft-rot", + "anthurium-downy-mildew-generic", + "anthurium-viral-leaf-curl", + "anthurium-wood-rot-decay", + "alocasia-bacterial-soft-rot", + "alocasia-downy-mildew-generic", + "alocasia-viral-leaf-curl", + "alocasia-wood-rot-decay", + "caladium-bacterial-soft-rot", + "caladium-downy-mildew-generic", + "caladium-viral-leaf-curl", + "caladium-wood-rot-decay", + "aglaonema-bacterial-soft-rot", + "aglaonema-downy-mildew-generic", + "aglaonema-viral-leaf-curl", + "aglaonema-wood-rot-decay", + "dieffenbachia-bacterial-soft-rot", + "dieffenbachia-downy-mildew-generic", + "dieffenbachia-viral-leaf-curl", + "dieffenbachia-wood-rot-decay", + "spathiphyllum-bacterial-soft-rot", + "spathiphyllum-downy-mildew-generic", + "spathiphyllum-viral-leaf-curl", + "spathiphyllum-wood-rot-decay", + "asparagus-bacterial-soft-rot", + "asparagus-downy-mildew-generic", + "asparagus-viral-leaf-curl", + "asparagus-wood-rot-decay", + "snake-plant-bacterial-soft-rot", + "snake-plant-downy-mildew-generic", + "snake-plant-viral-leaf-curl", + "snake-plant-wood-rot-decay", + "yucca-bacterial-soft-rot", + "yucca-downy-mildew-generic", + "yucca-viral-leaf-curl", + "yucca-wood-rot-decay", + "dracaena-bacterial-soft-rot", + "dracaena-downy-mildew-generic", + "dracaena-viral-leaf-curl", + "dracaena-wood-rot-decay", + "lily-of-the-valley-bacterial-soft-rot", + "lily-of-the-valley-downy-mildew-generic", + "lily-of-the-valley-viral-leaf-curl", + "lily-of-the-valley-wood-rot-decay", + "hosta-bacterial-soft-rot", + "hosta-downy-mildew-generic", + "hosta-viral-leaf-curl", + "hosta-wood-rot-decay", + "orchid-phalaenopsis-bacterial-soft-rot", + "orchid-phalaenopsis-downy-mildew-generic", + "orchid-phalaenopsis-viral-leaf-curl", + "orchid-phalaenopsis-wood-rot-decay", + "orchid-cattleya-bacterial-soft-rot", + "orchid-cattleya-downy-mildew-generic", + "orchid-cattleya-viral-leaf-curl", + "orchid-cattleya-wood-rot-decay", + "orchid-dendrobium-bacterial-soft-rot", + "orchid-dendrobium-downy-mildew-generic", + "orchid-dendrobium-viral-leaf-curl", + "orchid-dendrobium-wood-rot-decay", + "orchid-oncidium-bacterial-soft-rot", + "orchid-oncidium-downy-mildew-generic", + "orchid-oncidium-viral-leaf-curl", + "orchid-oncidium-wood-rot-decay", + "vanilla-bacterial-soft-rot", + "vanilla-downy-mildew-generic", + "vanilla-viral-leaf-curl", + "vanilla-wood-rot-decay", + "prickly-pear-bacterial-soft-rot", + "prickly-pear-downy-mildew-generic", + "prickly-pear-viral-leaf-curl", + "prickly-pear-wood-rot-decay", + "barrel-cactus-bacterial-soft-rot", + "barrel-cactus-downy-mildew-generic", + "barrel-cactus-viral-leaf-curl", + "barrel-cactus-wood-rot-decay", + "christmas-cactus-bacterial-soft-rot", + "christmas-cactus-downy-mildew-generic", + "christmas-cactus-viral-leaf-curl", + "christmas-cactus-wood-rot-decay", + "saguaro-bacterial-soft-rot", + "saguaro-downy-mildew-generic", + "saguaro-viral-leaf-curl", + "saguaro-wood-rot-decay", + "aloe-vera-bacterial-soft-rot", + "aloe-vera-downy-mildew-generic", + "aloe-vera-viral-leaf-curl", + "aloe-vera-wood-rot-decay", + "agave-bacterial-soft-rot", + "agave-downy-mildew-generic", + "agave-viral-leaf-curl", + "agave-wood-rot-decay", + "echeveria-bacterial-soft-rot", + "echeveria-downy-mildew-generic", + "echeveria-viral-leaf-curl", + "echeveria-wood-rot-decay", + "jade-plant-bacterial-soft-rot", + "jade-plant-downy-mildew-generic", + "jade-plant-viral-leaf-curl", + "jade-plant-wood-rot-decay", + "sedum-bacterial-soft-rot", + "sedum-downy-mildew-generic", + "sedum-viral-leaf-curl", + "sedum-wood-rot-decay", + "haworthia-bacterial-soft-rot", + "haworthia-downy-mildew-generic", + "haworthia-viral-leaf-curl", + "haworthia-wood-rot-decay", + "poinsettia-bacterial-soft-rot", + "poinsettia-downy-mildew-generic", + "poinsettia-viral-leaf-curl", + "poinsettia-wood-rot-decay", + "cassava-bacterial-soft-rot", + "cassava-downy-mildew-generic", + "cassava-viral-leaf-curl", + "cassava-wood-rot-decay", + "castor-bean-bacterial-soft-rot", + "castor-bean-downy-mildew-generic", + "castor-bean-viral-leaf-curl", + "castor-bean-wood-rot-decay", + "crown-of-thorns-bacterial-soft-rot", + "crown-of-thorns-downy-mildew-generic", + "crown-of-thorns-viral-leaf-curl", + "crown-of-thorns-wood-rot-decay", + "orange-bacterial-soft-rot", + "orange-downy-mildew-generic", + "orange-viral-leaf-curl", + "orange-wood-rot-decay", + "lemon-bacterial-soft-rot", + "lemon-downy-mildew-generic", + "lemon-viral-leaf-curl", + "lemon-wood-rot-decay", + "lime-bacterial-soft-rot", + "lime-downy-mildew-generic", + "lime-viral-leaf-curl", + "lime-wood-rot-decay", + "grapefruit-bacterial-soft-rot", + "grapefruit-downy-mildew-generic", + "grapefruit-viral-leaf-curl", + "grapefruit-wood-rot-decay", + "mandarin-bacterial-soft-rot", + "mandarin-downy-mildew-generic", + "mandarin-viral-leaf-curl", + "mandarin-wood-rot-decay", + "kumquat-bacterial-soft-rot", + "kumquat-downy-mildew-generic", + "kumquat-viral-leaf-curl", + "kumquat-wood-rot-decay", + "grape-bacterial-soft-rot", + "grape-downy-mildew-generic", + "grape-viral-leaf-curl", + "grape-wood-rot-decay", + "muscadine-bacterial-soft-rot", + "muscadine-downy-mildew-generic", + "muscadine-viral-leaf-curl", + "muscadine-wood-rot-decay", + "banana-bacterial-soft-rot", + "banana-downy-mildew-generic", + "banana-viral-leaf-curl", + "banana-wood-rot-decay", + "plantain-bacterial-soft-rot", + "plantain-downy-mildew-generic", + "plantain-viral-leaf-curl", + "plantain-wood-rot-decay", + "bird-of-paradise-bacterial-soft-rot", + "bird-of-paradise-downy-mildew-generic", + "bird-of-paradise-viral-leaf-curl", + "bird-of-paradise-wood-rot-decay", + "avocado-bacterial-soft-rot", + "avocado-downy-mildew-generic", + "avocado-viral-leaf-curl", + "avocado-wood-rot-decay", + "cinnamon-bacterial-soft-rot", + "cinnamon-downy-mildew-generic", + "cinnamon-viral-leaf-curl", + "cinnamon-wood-rot-decay", + "bay-laurel-bacterial-soft-rot", + "bay-laurel-downy-mildew-generic", + "bay-laurel-viral-leaf-curl", + "bay-laurel-wood-rot-decay", + "cocoa-bacterial-soft-rot", + "cocoa-downy-mildew-generic", + "cocoa-viral-leaf-curl", + "cocoa-wood-rot-decay", + "cotton-bacterial-soft-rot", + "cotton-downy-mildew-generic", + "cotton-viral-leaf-curl", + "cotton-wood-rot-decay", + "okra-bacterial-soft-rot", + "okra-downy-mildew-generic", + "okra-viral-leaf-curl", + "okra-wood-rot-decay", + "hibiscus-bacterial-soft-rot", + "hibiscus-downy-mildew-generic", + "hibiscus-viral-leaf-curl", + "hibiscus-wood-rot-decay", + "hollyhock-bacterial-soft-rot", + "hollyhock-downy-mildew-generic", + "hollyhock-viral-leaf-curl", + "hollyhock-wood-rot-decay", + "baobab-bacterial-soft-rot", + "baobab-downy-mildew-generic", + "baobab-viral-leaf-curl", + "baobab-wood-rot-decay", + "durian-bacterial-soft-rot", + "durian-downy-mildew-generic", + "durian-viral-leaf-curl", + "durian-wood-rot-decay", + "coconut-bacterial-soft-rot", + "coconut-downy-mildew-generic", + "coconut-viral-leaf-curl", + "coconut-wood-rot-decay", + "oil-palm-bacterial-soft-rot", + "oil-palm-downy-mildew-generic", + "oil-palm-viral-leaf-curl", + "oil-palm-wood-rot-decay", + "date-palm-bacterial-soft-rot", + "date-palm-downy-mildew-generic", + "date-palm-viral-leaf-curl", + "date-palm-wood-rot-decay", + "palm-areca-bacterial-soft-rot", + "palm-areca-downy-mildew-generic", + "palm-areca-viral-leaf-curl", + "palm-areca-wood-rot-decay", + "palm-parlor-bacterial-soft-rot", + "palm-parlor-downy-mildew-generic", + "palm-parlor-viral-leaf-curl", + "palm-parlor-wood-rot-decay", + "palm-kentia-bacterial-soft-rot", + "palm-kentia-downy-mildew-generic", + "palm-kentia-viral-leaf-curl", + "palm-kentia-wood-rot-decay", + "mango-bacterial-soft-rot", + "mango-downy-mildew-generic", + "mango-viral-leaf-curl", + "mango-wood-rot-decay", + "cashew-bacterial-soft-rot", + "cashew-downy-mildew-generic", + "cashew-viral-leaf-curl", + "cashew-wood-rot-decay", + "pistachio-bacterial-soft-rot", + "pistachio-downy-mildew-generic", + "pistachio-viral-leaf-curl", + "pistachio-wood-rot-decay", + "poison-ivy-bacterial-soft-rot", + "poison-ivy-downy-mildew-generic", + "poison-ivy-viral-leaf-curl", + "poison-ivy-wood-rot-decay", + "coffee-bacterial-soft-rot", + "coffee-downy-mildew-generic", + "coffee-viral-leaf-curl", + "coffee-wood-rot-decay", + "gardenia-bacterial-soft-rot", + "gardenia-downy-mildew-generic", + "gardenia-viral-leaf-curl", + "gardenia-wood-rot-decay", + "tea-bacterial-soft-rot", + "tea-downy-mildew-generic", + "tea-viral-leaf-curl", + "tea-wood-rot-decay", + "camellia-bacterial-soft-rot", + "camellia-downy-mildew-generic", + "camellia-viral-leaf-curl", + "camellia-wood-rot-decay", + "pine-bacterial-soft-rot", + "pine-downy-mildew-generic", + "pine-viral-leaf-curl", + "pine-wood-rot-decay", + "spruce-bacterial-soft-rot", + "spruce-downy-mildew-generic", + "spruce-viral-leaf-curl", + "spruce-wood-rot-decay", + "fir-bacterial-soft-rot", + "fir-downy-mildew-generic", + "fir-viral-leaf-curl", + "fir-wood-rot-decay", + "cedar-bacterial-soft-rot", + "cedar-downy-mildew-generic", + "cedar-viral-leaf-curl", + "cedar-wood-rot-decay", + "juniper-bacterial-soft-rot", + "juniper-downy-mildew-generic", + "juniper-viral-leaf-curl", + "juniper-wood-rot-decay", + "cypress-bacterial-soft-rot", + "cypress-downy-mildew-generic", + "cypress-viral-leaf-curl", + "cypress-wood-rot-decay", + "arborvitae-bacterial-soft-rot", + "arborvitae-downy-mildew-generic", + "arborvitae-viral-leaf-curl", + "arborvitae-wood-rot-decay", + "oak-bacterial-soft-rot", + "oak-downy-mildew-generic", + "oak-viral-leaf-curl", + "oak-wood-rot-decay", + "beech-bacterial-soft-rot", + "beech-downy-mildew-generic", + "beech-viral-leaf-curl", + "beech-wood-rot-decay", + "chestnut-bacterial-soft-rot", + "chestnut-downy-mildew-generic", + "chestnut-viral-leaf-curl", + "chestnut-wood-rot-decay", + "fiddle-leaf-fig-bacterial-soft-rot", + "fiddle-leaf-fig-downy-mildew-generic", + "fiddle-leaf-fig-viral-leaf-curl", + "fiddle-leaf-fig-wood-rot-decay", + "rubber-tree-bacterial-soft-rot", + "rubber-tree-downy-mildew-generic", + "rubber-tree-viral-leaf-curl", + "rubber-tree-wood-rot-decay", + "weeping-fig-bacterial-soft-rot", + "weeping-fig-downy-mildew-generic", + "weeping-fig-viral-leaf-curl", + "weeping-fig-wood-rot-decay", + "fig-bacterial-soft-rot", + "fig-downy-mildew-generic", + "fig-viral-leaf-curl", + "fig-wood-rot-decay", + "mulberry-bacterial-soft-rot", + "mulberry-downy-mildew-generic", + "mulberry-viral-leaf-curl", + "mulberry-wood-rot-decay", + "breadfruit-bacterial-soft-rot", + "breadfruit-downy-mildew-generic", + "breadfruit-viral-leaf-curl", + "breadfruit-wood-rot-decay", + "eucalyptus-bacterial-soft-rot", + "eucalyptus-downy-mildew-generic", + "eucalyptus-viral-leaf-curl", + "eucalyptus-wood-rot-decay", + "guava-bacterial-soft-rot", + "guava-downy-mildew-generic", + "guava-viral-leaf-curl", + "guava-wood-rot-decay", + "clove-bacterial-soft-rot", + "clove-downy-mildew-generic", + "clove-viral-leaf-curl", + "clove-wood-rot-decay", + "pineapple-bacterial-soft-rot", + "pineapple-downy-mildew-generic", + "pineapple-viral-leaf-curl", + "pineapple-wood-rot-decay", + "bromeliad-bacterial-soft-rot", + "bromeliad-downy-mildew-generic", + "bromeliad-viral-leaf-curl", + "bromeliad-wood-rot-decay", + "spanish-moss-bacterial-soft-rot", + "spanish-moss-downy-mildew-generic", + "spanish-moss-viral-leaf-curl", + "spanish-moss-wood-rot-decay", + "sweet-potato-bacterial-soft-rot", + "sweet-potato-downy-mildew-generic", + "sweet-potato-viral-leaf-curl", + "sweet-potato-wood-rot-decay", + "morning-glory-bacterial-soft-rot", + "morning-glory-downy-mildew-generic", + "morning-glory-viral-leaf-curl", + "morning-glory-wood-rot-decay", + "spinach-downy-mildew-generic", + "spinach-viral-leaf-curl", + "spinach-wood-rot-decay", + "swiss-chard-bacterial-soft-rot", + "swiss-chard-downy-mildew-generic", + "swiss-chard-viral-leaf-curl", + "swiss-chard-wood-rot-decay", + "beet-bacterial-soft-rot", + "beet-downy-mildew-generic", + "beet-viral-leaf-curl", + "beet-wood-rot-decay", + "quinoa-bacterial-soft-rot", + "quinoa-downy-mildew-generic", + "quinoa-viral-leaf-curl", + "quinoa-wood-rot-decay", + "amaranth-bacterial-soft-rot", + "amaranth-downy-mildew-generic", + "amaranth-viral-leaf-curl", + "amaranth-wood-rot-decay", + "rhubarb-bacterial-soft-rot", + "rhubarb-downy-mildew-generic", + "rhubarb-viral-leaf-curl", + "rhubarb-wood-rot-decay", + "buckwheat-bacterial-soft-rot", + "buckwheat-downy-mildew-generic", + "buckwheat-viral-leaf-curl", + "buckwheat-wood-rot-decay", + "papaya-bacterial-soft-rot", + "papaya-downy-mildew-generic", + "papaya-viral-leaf-curl", + "papaya-wood-rot-decay", + "olive-bacterial-soft-rot", + "olive-downy-mildew-generic", + "olive-viral-leaf-curl", + "olive-wood-rot-decay", + "jasmine-bacterial-soft-rot", + "jasmine-downy-mildew-generic", + "jasmine-viral-leaf-curl", + "jasmine-wood-rot-decay", + "lilac-bacterial-soft-rot", + "lilac-downy-mildew-generic", + "lilac-viral-leaf-curl", + "lilac-wood-rot-decay", + "ash-bacterial-soft-rot", + "ash-downy-mildew-generic", + "ash-viral-leaf-curl", + "ash-wood-rot-decay", + "hops-bacterial-soft-rot", + "hops-downy-mildew-generic", + "hops-viral-leaf-curl", + "hops-wood-rot-decay", + "hemp-bacterial-soft-rot", + "hemp-downy-mildew-generic", + "hemp-viral-leaf-curl", + "hemp-wood-rot-decay", + "fern-boston-bacterial-soft-rot", + "fern-boston-downy-mildew-generic", + "fern-boston-viral-leaf-curl", + "fern-boston-wood-rot-decay", + "fern-maidenhair-bacterial-soft-rot", + "fern-maidenhair-downy-mildew-generic", + "fern-maidenhair-viral-leaf-curl", + "fern-maidenhair-wood-rot-decay", + "spider-plant-bacterial-soft-rot", + "spider-plant-downy-mildew-generic", + "spider-plant-viral-leaf-curl", + "spider-plant-wood-rot-decay", + "zz-plant-bacterial-soft-rot", + "zz-plant-downy-mildew-generic", + "zz-plant-viral-leaf-curl", + "zz-plant-wood-rot-decay", + "prayer-plant-bacterial-soft-rot", + "prayer-plant-downy-mildew-generic", + "prayer-plant-viral-leaf-curl", + "prayer-plant-wood-rot-decay", + "calathea-bacterial-soft-rot", + "calathea-downy-mildew-generic", + "calathea-viral-leaf-curl", + "calathea-wood-rot-decay", + "pilea-bacterial-soft-rot", + "pilea-downy-mildew-generic", + "pilea-viral-leaf-curl", + "pilea-wood-rot-decay", + "tradescantia-bacterial-soft-rot", + "tradescantia-downy-mildew-generic", + "tradescantia-viral-leaf-curl", + "tradescantia-wood-rot-decay", + "succulent-echeveria-bacterial-soft-rot", + "succulent-echeveria-downy-mildew-generic", + "succulent-echeveria-viral-leaf-curl", + "succulent-echeveria-wood-rot-decay", + "money-tree-bacterial-soft-rot", + "money-tree-downy-mildew-generic", + "money-tree-viral-leaf-curl", + "money-tree-wood-rot-decay", + "palm-cat-bacterial-soft-rot", + "palm-cat-downy-mildew-generic", + "palm-cat-viral-leaf-curl", + "palm-cat-wood-rot-decay", + "ficus-altissima-bacterial-soft-rot", + "ficus-altissima-downy-mildew-generic", + "ficus-altissima-viral-leaf-curl", + "ficus-altissima-wood-rot-decay", + "string-of-pearls-bacterial-soft-rot", + "string-of-pearls-downy-mildew-generic", + "string-of-pearls-viral-leaf-curl", + "string-of-pearls-wood-rot-decay", + "burros-tail-bacterial-soft-rot", + "burros-tail-downy-mildew-generic", + "burros-tail-viral-leaf-curl", + "burros-tail-wood-rot-decay", + "snake-plant-masoniana-bacterial-soft-rot", + "snake-plant-masoniana-downy-mildew-generic", + "snake-plant-masoniana-viral-leaf-curl", + "snake-plant-masoniana-wood-rot-decay", + "passion-fruit-bacterial-soft-rot", + "passion-fruit-downy-mildew-generic", + "passion-fruit-viral-leaf-curl", + "passion-fruit-wood-rot-decay", + "kiwi-bacterial-soft-rot", + "kiwi-downy-mildew-generic", + "kiwi-viral-leaf-curl", + "kiwi-wood-rot-decay", + "lychee-bacterial-soft-rot", + "lychee-downy-mildew-generic", + "lychee-viral-leaf-curl", + "lychee-wood-rot-decay", + "rambutan-bacterial-soft-rot", + "rambutan-downy-mildew-generic", + "rambutan-viral-leaf-curl", + "rambutan-wood-rot-decay", + "jackfruit-bacterial-soft-rot", + "jackfruit-downy-mildew-generic", + "jackfruit-viral-leaf-curl", + "jackfruit-wood-rot-decay", + "dragon-fruit-bacterial-soft-rot", + "dragon-fruit-downy-mildew-generic", + "dragon-fruit-viral-leaf-curl", + "dragon-fruit-wood-rot-decay", + "pomegranate-bacterial-soft-rot", + "pomegranate-downy-mildew-generic", + "pomegranate-viral-leaf-curl", + "pomegranate-wood-rot-decay", + "persimmon-bacterial-soft-rot", + "persimmon-downy-mildew-generic", + "persimmon-viral-leaf-curl", + "persimmon-wood-rot-decay", + "tulip-bacterial-soft-rot", + "tulip-downy-mildew-generic", + "tulip-viral-leaf-curl", + "tulip-wood-rot-decay", + "daffodil-bacterial-soft-rot", + "daffodil-downy-mildew-generic", + "daffodil-viral-leaf-curl", + "daffodil-wood-rot-decay", + "iris-bacterial-soft-rot", + "iris-downy-mildew-generic", + "iris-viral-leaf-curl", + "iris-wood-rot-decay", + "lily-bacterial-soft-rot", + "lily-downy-mildew-generic", + "lily-viral-leaf-curl", + "lily-wood-rot-decay", + "peony-bacterial-soft-rot", + "peony-downy-mildew-generic", + "peony-viral-leaf-curl", + "peony-wood-rot-decay", + "hydrangea-bacterial-soft-rot", + "hydrangea-downy-mildew-generic", + "hydrangea-viral-leaf-curl", + "hydrangea-wood-rot-decay", + "rhododendron-bacterial-soft-rot", + "rhododendron-downy-mildew-generic", + "rhododendron-viral-leaf-curl", + "rhododendron-wood-rot-decay", + "azalea-bacterial-soft-rot", + "azalea-downy-mildew-generic", + "azalea-viral-leaf-curl", + "azalea-wood-rot-decay", + "magnolia-bacterial-soft-rot", + "magnolia-downy-mildew-generic", + "magnolia-viral-leaf-curl", + "magnolia-wood-rot-decay", + "dogwood-bacterial-soft-rot", + "dogwood-downy-mildew-generic", + "dogwood-viral-leaf-curl", + "dogwood-wood-rot-decay", + "maple-bacterial-soft-rot", + "maple-downy-mildew-generic", + "maple-viral-leaf-curl", + "maple-wood-rot-decay", + "birch-bacterial-soft-rot", + "birch-downy-mildew-generic", + "birch-viral-leaf-curl", + "birch-wood-rot-decay", + "elm-bacterial-soft-rot", + "elm-downy-mildew-generic", + "elm-viral-leaf-curl", + "elm-wood-rot-decay", + "willow-bacterial-soft-rot", + "willow-downy-mildew-generic", + "willow-viral-leaf-curl", + "willow-wood-rot-decay", + "poplar-bacterial-soft-rot", + "poplar-downy-mildew-generic", + "poplar-viral-leaf-curl", + "poplar-wood-rot-decay", + "sycamore-bacterial-soft-rot", + "sycamore-downy-mildew-generic", + "sycamore-viral-leaf-curl", + "sycamore-wood-rot-decay", + "hickory-bacterial-soft-rot", + "hickory-downy-mildew-generic", + "hickory-viral-leaf-curl", + "hickory-wood-rot-decay", + "pecan-bacterial-soft-rot", + "pecan-downy-mildew-generic", + "pecan-viral-leaf-curl", + "pecan-wood-rot-decay", + "walnut-bacterial-soft-rot", + "walnut-downy-mildew-generic", + "walnut-viral-leaf-curl", + "walnut-wood-rot-decay", + "fern-staghorn-root-rot-pythiumphytophthora", + "fern-staghorn-damping-off", + "fern-staghorn-gray-mold-botrytis-blight", + "fern-staghorn-mosaic-virus", + "fern-staghorn-wilt-fusarium-or-verticillium", + "fern-staghorn-root-knot-nematode", + "fern-staghorn-canker-stembranch", + "fern-staghorn-bacterial-soft-rot", + "fern-staghorn-downy-mildew-generic", + "fern-staghorn-viral-leaf-curl", + "fern-staghorn-wood-rot-decay", + "fern-birds-nest-root-rot-pythiumphytophthora", + "fern-birds-nest-damping-off", + "fern-birds-nest-gray-mold-botrytis-blight", + "fern-birds-nest-mosaic-virus", + "fern-birds-nest-wilt-fusarium-or-verticillium", + "fern-birds-nest-root-knot-nematode", + "fern-birds-nest-canker-stembranch", + "fern-birds-nest-bacterial-soft-rot", + "fern-birds-nest-downy-mildew-generic", + "fern-birds-nest-viral-leaf-curl", + "fern-birds-nest-wood-rot-decay", + "philodendron-brasil-root-rot-aroidsoverwatering", + "philodendron-brasil-root-rot-pythiumphytophthora", + "philodendron-brasil-damping-off", + "philodendron-brasil-gray-mold-botrytis-blight", + "philodendron-brasil-mosaic-virus", + "philodendron-brasil-wilt-fusarium-or-verticillium", + "philodendron-brasil-root-knot-nematode", + "philodendron-brasil-canker-stembranch", + "philodendron-brasil-bacterial-soft-rot", + "philodendron-brasil-downy-mildew-generic", + "philodendron-brasil-viral-leaf-curl", + "philodendron-brasil-wood-rot-decay", + "philodendron-monstera-root-rot-aroidsoverwatering", + "philodendron-monstera-root-rot-pythiumphytophthora", + "philodendron-monstera-damping-off", + "philodendron-monstera-gray-mold-botrytis-blight", + "philodendron-monstera-mosaic-virus", + "philodendron-monstera-wilt-fusarium-or-verticillium", + "philodendron-monstera-root-knot-nematode", + "philodendron-monstera-canker-stembranch", + "philodendron-monstera-bacterial-soft-rot", + "philodendron-monstera-downy-mildew-generic" + ], + "totalFound": 650 +} \ No newline at end of file diff --git a/apps/web/scripts/.image-results.json b/apps/web/scripts/.image-results.json index 67cadee..ec35ea6 100644 --- a/apps/web/scripts/.image-results.json +++ b/apps/web/scripts/.image-results.json @@ -293,5 +293,25 @@ "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Laboratory_outlines_in_plant_pathology_%28IA_laboratoryoutlin00whet%29.pdf/page1-500px-Laboratory_outlines_in_plant_pathology_%28IA_laboratoryoutlin00whet%29.pdf.jpg", "source": "commons", "quality": "good" + }, + "lettuce-tip-burn": { + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Medical_Heritage_Library_%28IA_63841040R.nlm.nih.gov%29.pdf/page1-500px-Medical_Heritage_Library_%28IA_63841040R.nlm.nih.gov%29.pdf.jpg", + "source": "commons", + "quality": "good" + }, + "cabbage-downy-mildew": { + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Chinakohl_3_Falscher_Mehltau-Brand.jpg/960px-Chinakohl_3_Falscher_Mehltau-Brand.jpg", + "source": "commons", + "quality": "good" + }, + "cabbage-fusarium-yellows": { + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Index_of_organisms_and_non-parasitic_diseases_in_Plant_disease_reporter%2C_supplements_XXXI-XXXVII%2C_1924_%28IA_indexoforganisms38vanm%29.pdf/page1-960px-Index_of_organisms_and_non-parasitic_diseases_in_Plant_disease_reporter%2C_supplements_XXXI-XXXVII%2C_1924_%28IA_indexoforganisms38vanm%29.pdf.jpg", + "source": "commons", + "quality": "good" + }, + "sunflower-downy-mildew": { + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Plasmopara_halstedii_R.H._07.jpg/960px-Plasmopara_halstedii_R.H._07.jpg", + "source": "commons", + "quality": "good" } } \ No newline at end of file diff --git a/apps/web/scripts/.image-review-needed.md b/apps/web/scripts/.image-review-needed.md new file mode 100644 index 0000000..220c3fd --- /dev/null +++ b/apps/web/scripts/.image-review-needed.md @@ -0,0 +1,136 @@ +# Disease Images — Human Review Needed + +Generated: 2026-06-06T14:20:43.602Z + +## Summary + +- Total diseases: 93 +- Good images (Wiki/Commons): 63 +- Fallback images (Brave): 30 +- Still missing: 0 + +## ⚠️ Fallback Images (Brave) — Review Required + +These 30 diseases have images from Brave Image Search. +Quality/relevance may be lower than Wikipedia/Commons sources. + +- **Cercospora Leaf Spot** (Cercospora iconia) on *basil* + ![](https://imgs.search.brave.com/qEe1QooFmBPBMvor3EDzfZP5vVYGlwOx7EytFqTviOQ/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9pbWFn/ZS5zbGlkZXNoYXJl/Y2RuLmNvbS9kaXNl/YXNlc29mYmFzaWxh/bmRtaW50LTE4MDYw/MjE2MzI1Ni83NS9E/aXNlYXNlcy1vZi1i/YXNpbC1hbmQtbWlu/dC0xMi0yMDQ4Lmpw/Zw) + URL: https://imgs.search.brave.com/qEe1QooFmBPBMvor3EDzfZP5vVYGlwOx7EytFqTviOQ/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9pbWFn/ZS5zbGlkZXNoYXJl/Y2RuLmNvbS9kaXNl/YXNlc29mYmFzaWxh/bmRtaW50LTE4MDYw/MjE2MzI1Ni83NS9E/aXNlYXNlcy1vZi1i/YXNpbC1hbmQtbWlu/dC0xMi0yMDQ4Lmpw/Zw + +- **Root Rot (Pythium)** (Pythium spp.) on *basil* + ![](https://imgs.search.brave.com/8Qv7FgXxZpOhf9FM12OLnGoyNFHqPJfn3HPWzcgiK-I/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly9iLnRo/dW1icy5yZWRkaXRt/ZWRpYS5jb20vWHU1/dnY4Q0Z1ZDZxZXZJ/YTVqTmdfNWtRbkZq/VXA1eFRMV19YYW5Y/U2NLVS5qcGc) + URL: https://imgs.search.brave.com/8Qv7FgXxZpOhf9FM12OLnGoyNFHqPJfn3HPWzcgiK-I/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly9iLnRo/dW1icy5yZWRkaXRt/ZWRpYS5jb20vWHU1/dnY4Q0Z1ZDZxZXZJ/YTVqTmdfNWtRbkZq/VXA1eFRMV19YYW5Y/U2NLVS5qcGc + +- **Black Spot** (Diplocarpon rosae) on *rose* + ![](https://imgs.search.brave.com/FhrhtzbypH95L6uCYLY8YVHAh9EohvnKUiARre4JB9g/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNS9Sb3NlLUZv/bGlhZ2Utd2l0aC1C/bGFjay1TcG90Lmpw/Zw) + URL: https://imgs.search.brave.com/FhrhtzbypH95L6uCYLY8YVHAh9EohvnKUiARre4JB9g/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNS9Sb3NlLUZv/bGlhZ2Utd2l0aC1C/bGFjay1TcG90Lmpw/Zw + +- **Leaf Spot (Cercospora)** (Cercospora spp.) on *monstera* + ![](https://imgs.search.brave.com/HxJxD9jMUqCj6btc1tedJw-fWV6HTCyiE2lEXkJ0_Pk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udXN1LmVk/dS9wbGFudGhlYWx0/aC9pcG0vaW1hZ2Vz/L2FncmljdWx0dXJh/bC92ZWdldGFibGVz/L0NlcmNvc3BvcmEt/bGVhZi1zcG90LXNw/aW5hY2guanBn) + URL: https://imgs.search.brave.com/HxJxD9jMUqCj6btc1tedJw-fWV6HTCyiE2lEXkJ0_Pk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udXN1LmVk/dS9wbGFudGhlYWx0/aC9pcG0vaW1hZ2Vz/L2FncmljdWx0dXJh/bC92ZWdldGFibGVz/L0NlcmNvc3BvcmEt/bGVhZi1zcG90LXNw/aW5hY2guanBn + +- **Cold Damage / Freeze Injury** (Abiotic temperature injury) on *monstera* + ![](https://imgs.search.brave.com/ershnjIJ0rMnhFoGVHhTYhwwjSy4UVehWwlJ9ZKF0FU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wcmV2/aWV3LnJlZGQuaXQv/aXMtdGhlcmUtaG9w/ZS1mb3ItbXktbW9u/c3RlcmEtdGhhdC1n/b3QtZnJvc3QtZGFt/YWdlLWlmLWFsbC12/MC1wcmswejc1cm1w/ZDYxLmpwZz93aWR0/aD02NDAmY3JvcD1z/bWFydCZhdXRvPXdl/YnAmcz1mYTVmOTMz/MTlkOWFiOTY3ZTlm/MzdkY2VhNDY5YjQ1/ODQ4NTNiYjMw) + URL: https://imgs.search.brave.com/ershnjIJ0rMnhFoGVHhTYhwwjSy4UVehWwlJ9ZKF0FU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wcmV2/aWV3LnJlZGQuaXQv/aXMtdGhlcmUtaG9w/ZS1mb3ItbXktbW9u/c3RlcmEtdGhhdC1n/b3QtZnJvc3QtZGFt/YWdlLWlmLWFsbC12/MC1wcmswejc1cm1w/ZDYxLmpwZz93aWR0/aD02NDAmY3JvcD1z/bWFydCZhdXRvPXdl/YnAmcz1mYTVmOTMz/MTlkOWFiOTY3ZTlm/MzdkY2VhNDY5YjQ1/ODQ4NTNiYjMw + +- **Spider Mite Infestation** (Tetranychus urticae) on *monstera* + ![](https://imgs.search.brave.com/TR9wjFRWkyus-jwsWOsygIVJbT44ddPEtvBxrYuf-RE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/c2hvcGlmeS5jb20v/cy9maWxlcy8xLzEy/ODkvMjA0MS9maWxl/cy9lYXJseS1zaWdu/cy1vZi1zcGlkZXIt/bWl0ZXMtMTRfMjA0/OHgyMDQ4LmpwZz92/PTE3MjY2NzAzNTc) + URL: https://imgs.search.brave.com/TR9wjFRWkyus-jwsWOsygIVJbT44ddPEtvBxrYuf-RE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/c2hvcGlmeS5jb20v/cy9maWxlcy8xLzEy/ODkvMjA0MS9maWxl/cy9lYXJseS1zaWdu/cy1vZi1zcGlkZXIt/bWl0ZXMtMTRfMjA0/OHgyMDQ4LmpwZz92/PTE3MjY2NzAzNTc + +- **Mealybug Infestation** (Pseudococcus longispinus) on *pothos* + ![](https://imgs.search.brave.com/9ObqyNw4LWLVwrKJNnXuXPYnk9ghE5IKK-IZRTDlErw/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5mb3JpbmRvb3Iu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIxLzA1L01lYWx5/YnVncy1vbi1wb3Ro/b3MuanBn) + URL: https://imgs.search.brave.com/9ObqyNw4LWLVwrKJNnXuXPYnk9ghE5IKK-IZRTDlErw/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5mb3JpbmRvb3Iu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIxLzA1L01lYWx5/YnVncy1vbi1wb3Ro/b3MuanBn + +- **Bacterial Soft Rot** (Erwinia carotovora) on *pothos* + ![](https://imgs.search.brave.com/zeGhUbP2_Dt05TgOWAP5DNItLTB9EaI4bWTcKecE-5U/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24uc2RzdGF0/ZS5lZHUvc2l0ZXMv/ZGVmYXVsdC9maWxl/cy9pbmxpbmUtaW1h/Z2VzL1ctMDE3MDkt/MDEtQmFjdGVyaWFs/LVNvZnQtUm90LVN5/bXB0b21zLVN0ZW0u/anBn) + URL: https://imgs.search.brave.com/zeGhUbP2_Dt05TgOWAP5DNItLTB9EaI4bWTcKecE-5U/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24uc2RzdGF0/ZS5lZHUvc2l0ZXMv/ZGVmYXVsdC9maWxl/cy9pbmxpbmUtaW1h/Z2VzL1ctMDE3MDkt/MDEtQmFjdGVyaWFs/LVNvZnQtUm90LVN5/bXB0b21zLVN0ZW0u/anBn + +- **Bacterial Leaf Spot** (Xanthomonas campestris pv. dieffenbachiae) on *peace-lily* + ![](https://imgs.search.brave.com/cnyXR2l1-H5EDwRDsAIxxf1aXwjzhnB2lcBzwWzLRu8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wbGFu/dGFtZXJpY2EuY29t/L3dwLWNvbnRlbnQv/dXBsb2Fkcy8yMDI0/LzA0L0xlYWYtU3Bv/dHMtb24tUGVhY2Ut/TGlseS1QbGFudC1B/bWVyaWNhLmpwZw) + URL: https://imgs.search.brave.com/cnyXR2l1-H5EDwRDsAIxxf1aXwjzhnB2lcBzwWzLRu8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wbGFu/dGFtZXJpY2EuY29t/L3dwLWNvbnRlbnQv/dXBsb2Fkcy8yMDI0/LzA0L0xlYWYtU3Bv/dHMtb24tUGVhY2Ut/TGlseS1QbGFudC1B/bWVyaWNhLmpwZw + +- **Botrytis Blight (Gray Mold)** (Botrytis cinerea) on *peace-lily* + ![](https://imgs.search.brave.com/Z3QnqltBrd5UYNFLiZp3t0oBQSJ2loklsrVSuIekQKg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/bW9zLmNtcy5mdXR1/cmVjZG4ubmV0L2h4/bXFBNWZORXk4NnJ0/YlRqUXlmdWYuanBn) + URL: https://imgs.search.brave.com/Z3QnqltBrd5UYNFLiZp3t0oBQSJ2loklsrVSuIekQKg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/bW9zLmNtcy5mdXR1/cmVjZG4ubmV0L2h4/bXFBNWZORXk4NnJ0/YlRqUXlmdWYuanBn + +- **Fungal Leaf Spot** (Alternaria / Cercospora spp.) on *orchid* + ![](https://imgs.search.brave.com/nwqmpPs_7Fgo5qbYAWXaGJIY4oNibmfZ8QE7RasMWUU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udW1kLmVk/dS9zaXRlcy9leHRl/bnNpb24udW1kLmVk/dS9maWxlcy9zdHls/ZXMvb3B0aW1pemVk/L3B1YmxpYy8yMDIx/LTAzL2hnaWNfaG91/c2VwbGFudF9mdW5n/YWxfbGVhZl9zcG90/X29yY2hpZC1IR0lD/LTEyNDItMDMxLXNs/aWRlLmpwZz9pdG9r/PVg2RTVQUGFk) + URL: https://imgs.search.brave.com/nwqmpPs_7Fgo5qbYAWXaGJIY4oNibmfZ8QE7RasMWUU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udW1kLmVk/dS9zaXRlcy9leHRl/bnNpb24udW1kLmVk/dS9maWxlcy9zdHls/ZXMvb3B0aW1pemVk/L3B1YmxpYy8yMDIx/LTAzL2hnaWNfaG91/c2VwbGFudF9mdW5n/YWxfbGVhZl9zcG90/X29yY2hpZC1IR0lD/LTEyNDItMDMxLXNs/aWRlLmpwZz9pdG9r/PVg2RTVQUGFk + +- **Mealybug Infestation** (Pseudococcus longispinus) on *succulent* + ![](https://imgs.search.brave.com/EhBP0Sxi3YMkWaLiAi9tyn-8Xle7Gl-KgEk8I9UNK2E/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/cG90YW5kYmxvb20u/Y29tL2Nkbi9zaG9w/L2FydGljbGVzL3Vu/bmFtZWRfOC5wbmc_/dj0xNjk1OTgyMzc3/JndpZHRoPTQ4MA) + URL: https://imgs.search.brave.com/EhBP0Sxi3YMkWaLiAi9tyn-8Xle7Gl-KgEk8I9UNK2E/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/cG90YW5kYmxvb20u/Y29tL2Nkbi9zaG9w/L2FydGljbGVzL3Vu/bmFtZWRfOC5wbmc_/dj0xNjk1OTgyMzc3/JndpZHRoPTQ4MA + +- **Gray Mold (Botrytis)** (Botrytis cinerea) on *strawberry* + ![](https://imgs.search.brave.com/xCkZV5hmL757LmnOpWFE0GJrqRqWlNC1H2z4TqS-v_8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/MC8wNi9Cb3RyeXRp/cy1HcmF5LU1vbGQt/b24tU3RyYXdiZXJy/eS1QbGFudHMuanBn) + URL: https://imgs.search.brave.com/xCkZV5hmL757LmnOpWFE0GJrqRqWlNC1H2z4TqS-v_8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/MC8wNi9Cb3RyeXRp/cy1HcmF5LU1vbGQt/b24tU3RyYXdiZXJy/eS1QbGFudHMuanBn + +- **Leaf Scorch (Phytophthora)** (Phytophthora fragariae) on *strawberry* + ![](https://imgs.search.brave.com/c5oBR4uLCQ_0ivGFHwPTQRPb7BVtyWk7fum-2U0UJSk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/YWNlcy5lZHUvd3At/Y29udGVudC91cGxv/YWRzLzIwMjUvMDgv/cGh5dG9waHRob3Jh/LXN0cmF3YmVycnkt/MS0zMDB4MzAwLmpw/Zw) + URL: https://imgs.search.brave.com/c5oBR4uLCQ_0ivGFHwPTQRPb7BVtyWk7fum-2U0UJSk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/YWNlcy5lZHUvd3At/Y29udGVudC91cGxv/YWRzLzIwMjUvMDgv/cGh5dG9waHRob3Jh/LXN0cmF3YmVycnkt/MS0zMDB4MzAwLmpw/Zw + +- **Gray Mold (Botrytis)** (Botrytis cinerea) on *lavender* + ![](https://imgs.search.brave.com/H2WYULyyPBsX20C-QDv3bL51oD2HbzLp8dxiwFGnrRc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/dHJlZWhvdXNlLmNv/L2pha2llLW9iamF3/eS1zemFyYS1wbGVz/bi5qcGc) + URL: https://imgs.search.brave.com/H2WYULyyPBsX20C-QDv3bL51oD2HbzLp8dxiwFGnrRc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/dHJlZWhvdXNlLmNv/L2pha2llLW9iamF3/eS1zemFyYS1wbGVz/bi5qcGc + +- **Root Rot (Phytophthora)** (Phytophthora spp.) on *lavender* + ![](https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n) + URL: https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n + +- **Fungal Leaf Spot** (Cercospora / Alternaria spp.) on *fiddle-leaf-fig* + ![](https://imgs.search.brave.com/0w67jSvytmrzD0aW7Pnj66RMkHm0t-1XB9XYMelmIFg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9iMjk0/NTA0MS5zbXVzaGNk/bi5jb20vMjk0NTA0/MS93cC1jb250ZW50/L3VwbG9hZHMvMjAx/OS8wMS9JTUdfOTc2/Mi0xLmpwZz9sb3Nz/eT0xJnN0cmlwPTEm/d2VicD0x) + URL: https://imgs.search.brave.com/0w67jSvytmrzD0aW7Pnj66RMkHm0t-1XB9XYMelmIFg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9iMjk0/NTA0MS5zbXVzaGNk/bi5jb20vMjk0NTA0/MS93cC1jb250ZW50/L3VwbG9hZHMvMjAx/OS8wMS9JTUdfOTc2/Mi0xLmpwZz9sb3Nz/eT0xJnN0cmlwPTEm/d2VicD0x + +- **Spider Mite Infestation** (Tetranychus urticae) on *fiddle-leaf-fig* + ![](https://imgs.search.brave.com/KsAcvKooEPszWNcjEkd60Yh0RwH9mkuAJYDyBk5bAYk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZG9zc2llcmJsb2cu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIwLzAyLzFoZWFk/ZXItdHJlYXQtc3Bp/ZGVyLW1pdGVzLWZp/ZGRsZS1pZy01MTJ4/NzY4LmpwZw) + URL: https://imgs.search.brave.com/KsAcvKooEPszWNcjEkd60Yh0RwH9mkuAJYDyBk5bAYk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZG9zc2llcmJsb2cu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIwLzAyLzFoZWFk/ZXItdHJlYXQtc3Bp/ZGVyLW1pdGVzLWZp/ZGRsZS1pZy01MTJ4/NzY4LmpwZw + +- **Mealybug Infestation** (Pseudococcus longispinus) on *aloe-vera* + ![](https://imgs.search.brave.com/L-rTzPOjDU_1iPtea1Jjj8Lfxfn9LG9UdkOJLG1rmlM/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4w/LnRoZWRhaWx5ZWNv/LmNvbS9lbi9wb3N0/cy85LzEvNi9sZWFm/X3Nwb3RfZGlzZWFz/ZV82MTlfMF82MDAu/anBn) + URL: https://imgs.search.brave.com/L-rTzPOjDU_1iPtea1Jjj8Lfxfn9LG9UdkOJLG1rmlM/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4w/LnRoZWRhaWx5ZWNv/LmNvbS9lbi9wb3N0/cy85LzEvNi9sZWFm/X3Nwb3RfZGlzZWFz/ZV82MTlfMF82MDAu/anBn + +- **Tobacco Mosaic Virus** (Tobacco mosaic virus (TMV)) on *jasmine* + ![](https://imgs.search.brave.com/m2lePdBbR7N-OUW_l4saDGEmJ-b6TKpv719fJAzP44A/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNy9KYXNtaW5l/LURpc2Vhc2VzLUZl/YXR1cmUuanBn) + URL: https://imgs.search.brave.com/m2lePdBbR7N-OUW_l4saDGEmJ-b6TKpv719fJAzP44A/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNy9KYXNtaW5l/LURpc2Vhc2VzLUZl/YXR1cmUuanBn + +- **Blossom End Rot** (Calcium deficiency disorder) on *chili* + ![](https://imgs.search.brave.com/G27ys6xN2xOxuIim46kDPLGDqsMGJkQCTYp4Qxnx3AE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/bWlzc291cmlib3Rh/bmljYWxnYXJkZW4u/b3JnL1BvcnRhbHMv/MC9HYXJkZW5pbmcv/R2FyZGVuaW5nJTIw/SGVscC9pbWFnZXMv/UGVzdHMvQmxvc3Nv/bV9FbmRfUm90X29m/X1RvbWF0bzIwNTku/anBn) + URL: https://imgs.search.brave.com/G27ys6xN2xOxuIim46kDPLGDqsMGJkQCTYp4Qxnx3AE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/bWlzc291cmlib3Rh/bmljYWxnYXJkZW4u/b3JnL1BvcnRhbHMv/MC9HYXJkZW5pbmcv/R2FyZGVuaW5nJTIw/SGVscC9pbWFnZXMv/UGVzdHMvQmxvc3Nv/bV9FbmRfUm90X29m/X1RvbWF0bzIwNTku/anBn + +- **Phomopsis Blight** (Phomopsis capsici) on *chili* + ![](https://imgs.search.brave.com/Bw2HKsqR-VUW2dIDs23YmpJOT2aVmbzbQljpQc-m6FY/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/Z2FyZGVuZXJzLmNv/bS9jZG4vc2hvcC9h/cnRpY2xlcy83Mjk2/LVBob21vcHNpcy1C/bGlnaHQtZWdncGxh/bnQuanBnP3Y9MTc1/NDkzODQ1NyZ3aWR0/aD0zMjA) + URL: https://imgs.search.brave.com/Bw2HKsqR-VUW2dIDs23YmpJOT2aVmbzbQljpQc-m6FY/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/Z2FyZGVuZXJzLmNv/bS9jZG4vc2hvcC9h/cnRpY2xlcy83Mjk2/LVBob21vcHNpcy1C/bGlnaHQtZWdncGxh/bnQuanBnP3Y9MTc1/NDkzODQ1NyZ3aWR0/aD0zMjA + +- **Blossom End Rot** (Calcium deficiency disorder) on *eggplant* + ![](https://imgs.search.brave.com/M5VmsJWcGGV3jd1JRaELFW-dUZY4QYEpeeBOY6s1ZpI/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93cGNk/bi53ZWIud3N1LmVk/dS9leHRlbnNpb24v/dXBsb2Fkcy9zaXRl/cy8zMS9wZXBwZXIt/Ymxvc3NvbS1lbmQt/cm90LTFMLTM5Nngy/OTAuanBn) + URL: https://imgs.search.brave.com/M5VmsJWcGGV3jd1JRaELFW-dUZY4QYEpeeBOY6s1ZpI/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93cGNk/bi53ZWIud3N1LmVk/dS9leHRlbnNpb24v/dXBsb2Fkcy9zaXRl/cy8zMS9wZXBwZXIt/Ymxvc3NvbS1lbmQt/cm90LTFMLTM5Nngy/OTAuanBn + +- **Verruculosis** (Phoma macdonaldii) on *eggplant* + ![](https://imgs.search.brave.com/jJoYdSyzMN7ZkgFRr1RDeFMSBitksjk9LTIAsCI1jHg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly90aHVt/YnMuZHJlYW1zdGlt/ZS5jb20vYi9sZWFm/LWVnZ3BsYW50LWFs/YmluaXNtLXN5bXB0/b20tbGVhZi1lZ2dw/bGFudC1hbGJpbmlz/bS1zeW1wdG9tLTQ1/MzYzOTQ3My5qcGc) + URL: https://imgs.search.brave.com/jJoYdSyzMN7ZkgFRr1RDeFMSBitksjk9LTIAsCI1jHg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly90aHVt/YnMuZHJlYW1zdGlt/ZS5jb20vYi9sZWFm/LWVnZ3BsYW50LWFs/YmluaXNtLXN5bXB0/b20tbGVhZi1lZ2dw/bGFudC1hbGJpbmlz/bS1zeW1wdG9tLTQ1/MzYzOTQ3My5qcGc + +- **Fern Rust** (Uromyces spp.) on *fern* + ![](https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc) + URL: https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc + +- **Root Rot (Crown Rot)** (Phytophthora / Pythium spp.) on *fern* + ![](https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n) + URL: https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n + +- **Powdery Mildew** (Erysiphe spp.) on *daisy* + ![](https://imgs.search.brave.com/AwTYk5ex0GDr38LBt-uk9Pi7yOic_sTSeiTVYlEtNW4/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly93d3cu/dW1hc3MuZWR1L2Fn/cmljdWx0dXJlLWZv/b2QtZW52aXJvbm1l/bnQvc2l0ZXMvZGVm/YXVsdC9maWxlcy9z/dHlsZXMvMTUweDE1/MC9wdWJsaWMvZmFj/dC1zaGVldHMvaW1h/Z2VzL3Bvd2Rlcnlf/bWlsZF8wMy5qcGc_/aXRvaz1kZTZVMFZ0/UA) + URL: https://imgs.search.brave.com/AwTYk5ex0GDr38LBt-uk9Pi7yOic_sTSeiTVYlEtNW4/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly93d3cu/dW1hc3MuZWR1L2Fn/cmljdWx0dXJlLWZv/b2QtZW52aXJvbm1l/bnQvc2l0ZXMvZGVm/YXVsdC9maWxlcy9z/dHlsZXMvMTUweDE1/MC9wdWJsaWMvZmFj/dC1zaGVldHMvaW1h/Z2VzL3Bvd2Rlcnlf/bWlsZF8wMy5qcGc_/aXRvaz1kZTZVMFZ0/UA + +- **Rust** (Puccinia spp.) on *daisy* + ![](https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc) + URL: https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc + +- **Downy Mildew** (Peronospora spp.) on *daisy* + ![](https://imgs.search.brave.com/h65q4ea2_EVIu5_NsJVPwUOVdrdVfhZGcr42TPFFEF0/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjQvMDkvZG93/bnktbWlsZGV3LXZl/Z2V0YWJsZS1nYXJk/ZW4uanBn) + URL: https://imgs.search.brave.com/h65q4ea2_EVIu5_NsJVPwUOVdrdVfhZGcr42TPFFEF0/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjQvMDkvZG93/bnktbWlsZGV3LXZl/Z2V0YWJsZS1nYXJk/ZW4uanBn + +- **Stem Rot (Fusarium)** (Fusarium spp.) on *cactus* + ![](https://imgs.search.brave.com/PF-Eqq7LSywJp8gzOgPppbHMfsXG4Ruj9zLZKkmxYRU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjMvMTIvRnVu/Z3VzLWRpc2Vhc2Uu/anBn) + URL: https://imgs.search.brave.com/PF-Eqq7LSywJp8gzOgPppbHMfsXG4Ruj9zLZKkmxYRU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjMvMTIvRnVu/Z3VzLWRpc2Vhc2Uu/anBn + diff --git a/apps/web/scripts/.plant-image-review-needed.md b/apps/web/scripts/.plant-image-review-needed.md new file mode 100644 index 0000000..c9455f9 --- /dev/null +++ b/apps/web/scripts/.plant-image-review-needed.md @@ -0,0 +1,10 @@ +# Plant Images - Still Missing + +Generated: 2026-06-06T17:08:24.166Z + +## Missing (4) + +- Calabash (Bottle Gourd) (calabash) +- ZZ Plant (zz-plant) +- Stromanthe Triostar (stromanthe) +- Shanghai Bok Choy (bok-choy-shanghai) diff --git a/apps/web/scripts/convert-keras-to-tfjs.py b/apps/web/scripts/convert-keras-to-tfjs.py new file mode 100644 index 0000000..0bc19b2 --- /dev/null +++ b/apps/web/scripts/convert-keras-to-tfjs.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +""" +Inspect and convert a .keras plant disease model to TF.js GraphModel format. + +Uses tensorflowjs_converter CLI to avoid Keras version deserialization issues. + +Usage: + pip3 install tensorflowjs # also pulls tensorflow as dependency + python3 scripts/convert-keras-to-tfjs.py +""" + +import json +import os +import shutil +import subprocess +import sys + +MODEL_PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "public", + "models", + "plant-disease-classifier", + "best_mnv2_pv_original.keras", +) + +OUTPUT_DIR = os.path.join( + os.path.dirname(MODEL_PATH), + "tfjs_model", +) + + +def inspect_keras_metadata(): + """Read .keras archive metadata without loading the model.""" + print("=" * 60) + print("MODEL INSPECTION (metadata only)") + print("=" * 60) + + try: + import zipfile + except ImportError: + print("ERROR: zipfile not available") + sys.exit(1) + + if not os.path.exists(MODEL_PATH): + print(f"ERROR: Model not found at {MODEL_PATH}") + sys.exit(1) + + print(f"\nModel file: {MODEL_PATH}") + print( + f"File size: {os.path.getsize(MODEL_PATH):,} bytes ({os.path.getsize(MODEL_PATH) / 1024 / 1024:.1f} MB)" + ) + + # .keras files are ZIP archives + with zipfile.ZipFile(MODEL_PATH) as zf: + names = zf.namelist() + print(f"\nArchive contents ({len(names)} entries):") + for name in names: + info = zf.getinfo(name) + print(f" {name:<40s} {info.file_size:>10,} bytes") + + # Read config.json for model architecture info + config_path = None + for name in names: + if name.endswith("config.json"): + config_path = name + break + + if config_path: + print(f"\nReading {config_path}...") + with zf.open(config_path) as f: + config = json.load(f) + + # Extract key info + model_type = config.get("class_name", "unknown") + print(f"Model class: {model_type}") + + # Try to find output layer info + if "config" in config: + inner_config = config["config"] + + # Look for output shape in config + if "output_shape" in inner_config: + print(f"Output shape: {inner_config['output_shape']}") + + # Look through layers for the final dense layer + if "layers" in inner_config: + layers = inner_config["layers"] + print(f"\nLayers ({len(layers)} total):") + for layer in layers: + layer_name = layer.get("config", {}).get("name", "?") + layer_class = layer.get("class_name", "?") + layer_module = layer.get("module", "?") + + # Extract units/activation for dense layers + layer_config = layer.get("config", {}) + units = layer_config.get("units") + activation = layer_config.get("activation") + + detail = "" + if units: + detail = f" units={units}" + if activation: + detail += f" activation={activation}" + + print(f" {layer_name:<30s} {layer_class:<20s}{detail}") + + # Find last dense layer for class count + for layer in reversed(layers): + if layer.get("class_name") == "Dense": + units = layer.get("config", {}).get("units") + activation = layer.get("config", {}).get("activation") + print("\nClassification head:") + print(f" Units (classes): {units}") + print(f" Activation: {activation}") + print( + f" Layer name: {layer.get('config', {}).get('name', '?')}" + ) + break + + # Check compile config + if "compile_config" in config: + compile_cfg = config["compile_config"] + optimizer = compile_cfg.get("optimizer", {}) + if isinstance(optimizer, dict): + opt_name = optimizer.get("class_name", "?") + lr = optimizer.get("config", {}).get("learning_rate") + print("\nTraining config:") + print(f" Optimizer: {opt_name}") + if lr: + print(f" Learning rate: {lr}") + loss = compile_cfg.get("loss", "?") + metrics = compile_cfg.get("metrics", []) + print(f" Loss: {loss}") + print(f" Metrics: {metrics}") + + # Check input shape + if "build_config" in config: + build_cfg = config["build_config"] + if "input_shape" in build_cfg: + print(f"\nInput shape: {build_cfg['input_shape']}") + + +def convert_to_tfjs(): + """Convert using tensorflowjs_converter CLI.""" + print("\n" + "=" * 60) + print("CONVERTING TO TF.JS GRAPH MODEL") + print("=" * 60) + + # Check tensorflowjs_converter CLI is available + converter = shutil.which("tensorflowjs_converter") + if not converter: + print("ERROR: tensorflowjs_converter not found in PATH.") + print(" pip3 install tensorflowjs") + sys.exit(1) + + # Clean output dir + if os.path.exists(OUTPUT_DIR): + print(f"Removing existing output dir: {OUTPUT_DIR}") + shutil.rmtree(OUTPUT_DIR) + + os.makedirs(OUTPUT_DIR, exist_ok=True) + + print(f"\nConverting {MODEL_PATH} -> {OUTPUT_DIR}/") + print("(this may take a minute...)") + + # Use the venv's python to run the converter (avoids import issues) + python_exe = sys.executable # the python running this script + result = subprocess.run( + [ + python_exe, + "-m", + "tensorflowjs.converters.converter", + "--input_format=keras", + "--output_format=tfjs_graph_model", + MODEL_PATH, + OUTPUT_DIR, + ], + capture_output=True, + text=True, + timeout=300, + ) + + if result.returncode != 0: + print("\nERROR: Conversion failed!") + print(f"stdout: {result.stdout}") + print(f"stderr: {result.stderr}") + sys.exit(1) + + if result.stdout: + print(result.stdout) + if result.stderr: + # Some warnings are normal + print(f"Converter output: {result.stderr}") + + # Verify output + model_json_path = os.path.join(OUTPUT_DIR, "model.json") + if not os.path.exists(model_json_path): + print("ERROR: Conversion did not produce model.json") + sys.exit(1) + + # List output files + files = os.listdir(OUTPUT_DIR) + total_size = sum( + os.path.getsize(os.path.join(OUTPUT_DIR, f)) + for f in files + if os.path.isfile(os.path.join(OUTPUT_DIR, f)) + ) + + print("\nConversion complete!") + print(f"Output directory: {OUTPUT_DIR}/") + print(f"Files: {len(files)}") + for f in sorted(files): + fpath = os.path.join(OUTPUT_DIR, f) + if os.path.isfile(fpath): + size = os.path.getsize(fpath) + print(f" {f:<30s} {size:>10,} bytes") + print(f"Total size: {total_size:,} bytes ({total_size / 1024 / 1024:.1f} MB)") + + # Read model.json to check config + with open(model_json_path) as f: + model_json = json.load(f) + + print(f"\nTF.js model format: {model_json.get('format', 'unknown')}") + print(f"Generated by: {model_json.get('generatedBy', 'unknown')}") + + # Inspect model topology + if "modelTopology" in model_json: + topology = model_json["modelTopology"] + print("\nModel topology:") + print(f" Name: {topology.get('model_name', 'unnamed')}") + print(f" Ops: {len(topology.get('node', []))} nodes") + + # Input/output nodes + inputs = topology.get("inputs", {}) + outputs = topology.get("outputs", {}) + print(f" Inputs: {list(inputs.keys())}") + for name, info in inputs.items(): + shape = info.get("tensorShape", {}) + print(f" {name}: shape={shape.get('dim', 'unknown')}") + print(f" Outputs: {list(outputs.keys())}") + for name, info in outputs.items(): + shape = info.get("tensorShape", {}) + print(f" {name}: shape={shape.get('dim', 'unknown')}") + + # Check weights specification + if "weightsManifest" in model_json: + manifest = model_json["weightsManifest"] + print(f"\nWeight manifests: {len(manifest)}") + for i, m in enumerate(manifest): + shards = m.get("shards", []) + print(f" Manifest {i}: {len(shards)} shard(s)") + + return OUTPUT_DIR + + +def main(): + if not os.path.exists(MODEL_PATH): + print(f"ERROR: Model not found at {MODEL_PATH}") + sys.exit(1) + + # Step 1: Inspect metadata + inspect_keras_metadata() + + # Step 2: Convert + output_dir = convert_to_tfjs() + + # Step 3: Summary + print("\n" + "=" * 60) + print("NEXT STEPS") + print("=" * 60) + print(f""" +1. Move the TF.js model to the expected location: + The model-loader expects model.json at: + public/models/plant-disease-classifier/model.json + + Move files: + mv {output_dir}/model.json public/models/plant-disease-classifier/ + mv {output_dir}/group1-shard* public/models/plant-disease-classifier/ + +2. IMPORTANT: This model has 38 output classes (original PlantVillage). + Your labels.ts expects 95 classes (93 diseases + healthy + unknown). + You'll need to either: + a) Fine-tune the model with your 95-class dataset, OR + b) Map the 38 PlantVillage classes to your disease IDs + +3. Install @tensorflow/tfjs in your project: + npm install @tensorflow/tfjs + +4. Test with your API: + npm run dev + POST /api/identify with an uploaded image +""") + + +if __name__ == "__main__": + main() diff --git a/apps/web/scripts/expand-diseases.ts b/apps/web/scripts/expand-diseases.ts new file mode 100644 index 0000000..43e5317 --- /dev/null +++ b/apps/web/scripts/expand-diseases.ts @@ -0,0 +1,691 @@ +/** + * Expand DB with comprehensive plant disease list from Wikipedia. + * + * Reads /tmp/plant_diseases/plant_diseases_comprehensive.txt, + * compares against existing DB entries (by name, case-insensitive), + * and inserts new entries with reasonable defaults. + * + * Usage: + * cd apps/web && export $(grep -v '^#' .env.development | xargs) && npx tsx scripts/expand-diseases.ts + */ + +import "dotenv/config"; +import { readFileSync } from "fs"; +import { eq, sql } from "drizzle-orm"; +import { getDb, closeDb } from "../src/lib/db/index"; +import { plants, diseases } from "../src/lib/db/schema"; +import type { CausalAgentType, Severity } from "../src/lib/types"; + +// ─── Parse the comprehensive list ───────────────────────────────────────────── + +interface DiseaseEntry { + name: string; + sourceUrl: string; +} + +function parseComprehensiveList(filePath: string): DiseaseEntry[] { + const content = readFileSync(filePath, "utf-8"); + const entries: DiseaseEntry[] = []; + const lines = content.split("\n"); + const nameRe = /^\d+\.\s+(.+)$/; + + for (let i = 0; i < lines.length; i++) { + const nameMatch = lines[i].match(nameRe); + if (nameMatch) { + const name = nameMatch[1].trim(); + const urlLine = lines[i + 1]?.trim() || ""; + // Only add if the next line is a valid URL + if (urlLine.startsWith("http")) { + entries.push({ name, sourceUrl: urlLine }); + i++; // skip the URL line + } else { + entries.push({ name, sourceUrl: "" }); + } + } + } + return entries; +} + +// ─── Infer causal agent type from disease name ──────────────────────────────── + +function inferCausalAgent(name: string): CausalAgentType { + const lower = name.toLowerCase(); + + // Bacterial indicators + if ( + lower.startsWith("bacterial ") || + lower.includes(" xanthomonas") || + lower.includes(" pseudomonas") || + lower.includes(" erwinia") || + lower.includes(" ralstonia") || + lower.includes(" clavibacter") || + lower.includes(" streptomyces") || + lower.includes(" agrobacterium") || + lower.includes(" corynebacterium") || + lower.includes(" pectobacterium") || + lower.includes(" dickeya") + ) { + return "bacterial"; + } + + // Viral indicators - strong signals + if ( + lower.includes(" mosaic") || + lower.includes(" yellows") || + lower.includes(" leaf roll") || + lower.includes(" leafroll") || + lower.includes(" ringspot") || + lower.includes(" ring spot") || + lower.includes(" enation") || + lower.includes(" phyllody") || + lower.includes(" witches") || + lower.includes(" witches'") || + lower.includes(" crinkle") || + lower.includes(" rosette") || + lower.includes(" shoestring") || + lower.includes(" tristeza") || + lower.includes(" psorosis") || + lower.includes(" stubborn") || + lower.includes(" greening") || + lower.includes(" vein banding") || + lower.includes(" vein mottle") || + lower.includes(" vein clearing") || + lower.includes(" leaf pucker") || + lower.includes(" pucker leaf") || + lower.includes(" latent") || + lower.includes(" motley") || + lower.includes(" rugose") + ) { + return "viral"; + } + + // Viral - names containing "virus" or "viroid" + if (lower.includes(" virus") || lower.includes(" viroid") || lower.includes(" virosis")) { + return "viral"; + } + + // Nematodes + if ( + lower.includes(" nematode") || + lower.includes(" nematodes") || + lower.includes(" eelworm") || + lower.includes(" root knot") || + lower.includes(" root-knot") || + lower.includes(" cyst ") || + lower.includes(" dagger ") || + lower.includes(" lance ") || + lower.includes(" lesion ") || + lower.includes(" ring ") || + lower.includes(" spiral ") || + lower.includes(" sting ") || + lower.includes(" stubby ") || + lower.includes(" needle ") || + lower.includes(" foliar ") || + lower.includes(" bulb ") || + lower.includes(" reniform ") || + lower.includes(" burrowing ") + ) { + // Check if it's really a nematode name + if (lower.includes("nematode")) return "environmental"; + } + + // Fungal indicators + if ( + lower.includes(" mildew") || + lower.includes(" rust") || + lower.includes(" smut") || + lower.includes(" blight") || + lower.includes(" canker") || + lower.includes(" rot") || + lower.includes(" scab") || + lower.includes(" mold") || + lower.includes(" anthracnose") || + lower.includes(" bunt") || + lower.includes(" ergot") || + lower.includes(" dieback") || + lower.includes(" scald") || + lower.includes(" blotch") || + lower.includes(" speckle") || + lower.includes(" sooty") || + lower.includes(" flyspeck") || + lower.includes(" fusarium") || + lower.includes(" alternaria") || + lower.includes(" botrytis") || + lower.includes(" rhizoctonia") || + lower.includes(" pythium") || + lower.includes(" phytophthora") || + lower.includes(" sclerotinia") || + lower.includes(" verticillium") || + lower.includes(" ascochyta") || + lower.includes(" cercospora") || + lower.includes(" septoria") || + lower.includes(" colletotrichum") || + lower.includes(" phomopsis") || + lower.includes(" diaporthe") || + lower.includes(" diplodia") || + lower.includes(" macrophomina") || + lower.includes(" cylindrocladium") || + lower.includes(" mycosphaerella") || + lower.includes(" helminthosporium") || + lower.includes(" curvularia") || + lower.includes(" bipolaris") || + lower.includes(" exserohilum") || + lower.includes(" dothiorella") || + lower.includes(" fusicoccum") || + lower.includes(" pestalotia") || + lower.includes(" glomerella") || + lower.includes(" nectria") || + lower.includes(" eutypa") || + lower.includes(" armillaria") || + lower.includes(" ganoderma") || + lower.includes(" phoma") || + lower.includes(" cladosporium") || + lower.includes(" penicillium") || + lower.includes(" aspergillus") || + lower.includes(" rhizopus") || + lower.includes(" mucor") || + lower.includes(" downy mildew") || + lower.includes(" powdery mildew") || + lower.includes(" pink rot") || + lower.includes(" pink mold") || + lower.includes(" pink root") || + lower.includes(" gray mold") || + lower.includes(" grey mold") || + lower.includes(" white rot") || + lower.includes(" white mold") || + lower.includes(" brown rot") || + lower.includes(" black rot") || + lower.includes(" soft rot") || + lower.includes(" dry rot") || + lower.includes(" fruit rot") || + lower.includes(" root rot") || + lower.includes(" stem rot") || + lower.includes(" ear rot") || + lower.includes(" crown rot") || + lower.includes(" collar rot") || + lower.includes(" pod rot") || + lower.includes(" kernel rot") || + lower.includes(" stalk rot") || + lower.includes(" head rot") || + lower.includes(" butt rot") || + lower.includes(" stump rot") || + lower.includes(" wood rot") || + lower.includes(" seed rot") || + lower.includes(" leaf spot") || + lower.includes(" leaf blight") || + lower.includes(" leaf blotch") || + lower.includes(" leaf rust") || + lower.includes(" brown spot") || + lower.includes(" black spot") || + lower.includes(" black leg") || + lower.includes(" blackleg") || + lower.includes(" black foot") || + lower.includes(" white rust") || + lower.includes(" white smut") || + lower.includes(" white scab") || + lower.includes(" tar spot") || + lower.includes(" target spot") || + lower.includes(" dollar spot") || + lower.includes(" fairy ring") || + lower.includes(" snow mold") || + lower.includes(" pink disease") || + lower.includes(" thread blight") || + lower.includes(" web blight") || + lower.includes(" sclerotial") || + lower.includes(" sore shin") || + lower.includes(" wart") || + lower.includes(" scurf") || + lower.includes(" silver scurf") || + lower.includes(" shot hole") || + lower.includes(" timber rot") || + lower.includes(" cottony rot") || + lower.includes(" watery rot") || + lower.includes(" sour rot") || + lower.includes(" seepage") || + lower.includes(" bunch rot") || + lower.includes(" noble rot") || + lower.includes(" bitter rot") || + lower.includes(" ripe rot") || + lower.includes(" ring rot") || + lower.includes(" coral spot") || + lower.includes(" stem canker") || + lower.includes(" branch canker") || + lower.includes(" perennial canker") || + lower.includes(" brand canker") || + lower.includes(" blister canker") || + lower.includes(" bleeding canker") || + lower.includes(" bark canker") || + lower.includes(" gum canker") || + lower.includes(" collar crack") || + lower.includes(" fasciation") || + lower.includes(" exobasidium") || + lower.includes(" mycorrhiza") || + lower.includes(" lichen") || + lower.includes(" algal") || + lower.includes(" chlorosis") || + lower.includes(" leaf blister") || + lower.includes(" leaf curl") + ) { + return "fungal"; + } + + // Physiological / environmental indicators + if ( + lower.includes(" sunscald") || + lower.includes(" sunburn") || + lower.includes(" chilling") || + lower.includes(" blossom end rot") || + lower.includes(" edema") || + lower.includes(" deficiency") || + lower.includes(" toxicity") || + lower.includes(" ozone") || + lower.includes(" drought") || + lower.includes(" frost") || + lower.includes(" herbicide") || + lower.includes(" pesticide") || + lower.includes(" phytotoxicity") || + lower.includes(" catface") || + lower.includes(" fruit cracking") || + lower.includes(" russeting") || + lower.includes(" growth crack") || + lower.includes(" mealiness") || + lower.includes(" wind scar") || + lower.includes(" hail") || + lower.includes(" salt ") || + lower.includes(" nutritional") || + lower.includes(" mineral") || + lower.includes(" overwatering") || + lower.includes(" under watering") || + lower.includes(" waterlogging") || + lower.includes(" chemical injury") || + lower.includes(" spray injury") || + lower.includes(" fertilizer burn") || + lower.includes(" lightning") || + lower.includes(" bruising") || + lower.includes(" pressure bruise") || + lower.includes(" impact damage") || + lower.includes(" transit rot") + ) { + return "environmental"; + } + + // Insect/mite/pest indicators + if ( + lower.includes(" mite") || + lower.includes(" beetle") || + lower.includes(" weevil") || + lower.includes(" aphid") || + lower.includes(" bollworm") || + lower.includes(" leaf miner") || + lower.includes(" mealybug") || + lower.includes(" thrips") || + lower.includes(" whitefly") || + lower.includes(" caterpillar") || + lower.includes(" sawfly") || + lower.includes(" scale ") || + lower.includes(" leafhopper") || + lower.includes(" psylla") || + lower.includes(" slug") || + lower.includes(" snail") || + lower.includes(" borer") || + lower.includes(" maggot") || + lower.includes(" grub") || + lower.includes(" earwig") || + lower.includes(" grasshopper") + ) { + return "environmental"; + } + + // Fungal genus names + const fungalGenera = [ + "armillaria", + "aspergillus", + "alternaria", + "botrytis", + "cercospora", + "cladosporium", + "colletotrichum", + "curvularia", + "cylindrocladium", + "diplodia", + "fusarium", + "ganoderma", + "glomerella", + "helminthosporium", + "macrophomina", + "mycosphaerella", + "nectria", + "penicillium", + "pestalotia", + "phoma", + "phomopsis", + "phytophthora", + "pythium", + "rhizoctonia", + "sclerotinia", + "septoria", + "verticillium", + "ascochyta", + "cercoseptoria", + "phaeoisariopsis", + "phaeoseptoria", + "stagonospora", + "stemphylium", + "myrothecium", + "myriogenospora", + "dactuliophora", + "dilophospora", + "coniothecium", + "coniosporium", + "cryptostictis", + "catacauma", + "botryodiplodia", + "botryosphaeria", + "cephalosporium", + "ceratocystis", + "chalara", + "choanephora", + "clitocybe", + "coprinus", + "cordana", + "corticium", + "corynespora", + "coryneum", + "cylindrocarpon", + "cylindrocladiella", + "cylindrosporium", + "cytospora", + "cytosporina", + "dematophora", + "didymella", + "dothiorella", + "drechslera", + "endothia", + "eutypa", + "eutypella", + "exobasidium", + "fusicladium", + "fusicoccum", + "gibberella", + "glomerella", + "gnomonia", + "graphiola", + "guignardia", + "hendersonia", + "hendersonula", + "hymenochaete", + "hypoxylon", + "lasiodiplodia", + "leptosphaeria", + "leucostoma", + "lophodermium", + "macrophoma", + "marasmiellus", + "marasmius", + "massaria", + "monilia", + "monosporascus", + "mystrosporium", + "neocosmospora", + "nigrospora", + "omphalia", + "ophiobolus", + "ovulinia", + "ozonium", + "panagrolaimus", + "periconia", + "pestalosphaeria", + "pestalotiopsis", + "phialophora", + "phymatotrichum", + "physalospora", + "phytophthora", + "plasmodiophora", + "plectosporium", + "polyporus", + "poria", + "pseudocercosporella", + "pseudopeziza", + "pseudoseptoria", + "puccinia", + "pyrenochaeta", + "pythium", + "ramularia", + "rhizoctonia", + "rhizopus", + "rhynchosporium", + "rosellinia", + "sclerophthora", + "sclerotinia", + "sclerotium", + "septoria", + "sphaceloma", + "sphaeropsis", + "spongospora", + "stagonospora", + "stemphylium", + "stereum", + "stigmina", + "thanatephorus", + "thielaviopsis", + "tippula", + "typhula", + "ulocladium", + "uredo", + "ustilago", + "valsa", + "venturia", + "verticillium", + "xylaria", + ]; + for (const genus of fungalGenera) { + if (lower.includes(genus)) return "fungal"; + } + + // Default to fungal (most plant diseases are fungal) + return "fungal"; +} + +// ─── Infer severity ─────────────────────────────────────────────────────────── + +function inferSeverity(name: string): Severity { + const lower = name.toLowerCase(); + if ( + lower.includes(" lethal") || + lower.includes(" devastating") || + lower.includes(" destructive") || + lower.includes(" fatal") || + lower.includes(" severe") || + lower.includes(" blight") || + lower.includes(" wilt") || + lower.includes(" canker") || + lower.includes(" dieback") || + lower.includes(" decline") || + lower.includes(" rot") || + lower.includes(" gall") || + lower.includes(" gummosis") || + lower.includes(" necrosis") || + lower.includes(" erwinia") + ) { + return "high"; + } + if ( + lower.includes(" minor") || + lower.includes(" mild") || + lower.includes(" slight") || + lower.includes(" speckle") || + lower.includes(" fleck") || + lower.includes(" freckle") || + lower.includes(" chlorosis") || + lower.includes(" translucence") || + lower.includes(" superficial") + ) { + return "low"; + } + return "moderate"; +} + +// ─── Generate a deterministic slug ──────────────────────────────────────────── + +function toSlug(name: string): string { + return ( + "wiki-" + + name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, "") + .replace(/-+/g, "-") + ); +} + +// ─── Main ───────────────────────────────────────────────────────────────────── + +async function main() { + const db = getDb(); + + // 1. Get existing disease names from DB + const existingDiseases = await db.select({ name: diseases.name }).from(diseases); + const existingNames = new Set(existingDiseases.map((d) => d.name.toLowerCase().trim())); + + console.log(`Existing diseases in DB: ${existingNames.size}`); + + // 2. Parse the comprehensive list + const entries = parseComprehensiveList("/tmp/plant_diseases/plant_diseases_comprehensive.txt"); + console.log(`Total entries in comprehensive file: ${entries.length}`); + + // 3. Find or create catch-all plants + for (const plantId of ["general", "unknown"]) { + const existing = await db.select().from(plants).where(eq(plants.id, plantId)).get(); + + if (!existing) { + console.log(`Creating '${plantId}' plant for catch-all diseases...`); + await db.insert(plants).values({ + id: plantId, + commonName: plantId === "general" ? "General (Multiple Plants)" : "Unknown Plant", + scientificName: "Various", + family: "Various", + category: "houseplant", + careSummary: + plantId === "general" + ? "General plant diseases affecting multiple species." + : "Plant disease with unknown host plant.", + imageUrl: "", + }); + console.log(`Created '${plantId}' plant.`); + } + } + + // 4. Filter new entries (deduplicate within file + against DB) + const newEntries: DiseaseEntry[] = []; + const skipped: string[] = []; + const seen = new Set(); + + for (const entry of entries) { + const key = entry.name.toLowerCase().trim(); + if (seen.has(key)) continue; + seen.add(key); + + if (existingNames.has(key)) { + skipped.push(entry.name); + } else { + newEntries.push(entry); + } + } + + console.log(`\nNew entries to insert: ${newEntries.length}`); + console.log(`Already existing (skipped): ${skipped.length}`); + + if (skipped.length > 0) { + console.log(`\nFirst 10 skipped (of ${skipped.length}):`); + skipped.slice(0, 10).forEach((s) => console.log(` - ${s}`)); + } + + // 5. Insert new entries in batches + if (newEntries.length === 0) { + console.log("\n✅ No new diseases to insert."); + closeDb(); + return; + } + + const BATCH_SIZE = 50; + let inserted = 0; + let errors = 0; + + for (let i = 0; i < newEntries.length; i += BATCH_SIZE) { + const batch = newEntries.slice(i, i + BATCH_SIZE); + const values = batch.map((entry) => { + const causalAgent = inferCausalAgent(entry.name); + const severity = inferSeverity(entry.name); + return { + id: toSlug(entry.name), + plantId: "general", + name: entry.name, + scientificName: "", + causalAgentType: causalAgent, + description: `A plant disease known as "${entry.name}". Source: Wikipedia.`, + symptoms: [], + causes: [], + treatment: [], + prevention: [], + lookalikeIds: [], + severity, + sourceUrl: entry.sourceUrl, + imageUrl: "", + }; + }); + + try { + await db.insert(diseases).values(values).onConflictDoNothing(); + inserted += values.length; + } catch (err) { + // Fall back to individual inserts for this batch if batch fails + console.log(` Batch failed, trying individually...`); + for (const val of values) { + try { + await db.insert(diseases).values(val).onConflictDoNothing(); + inserted++; + } catch (e2) { + // If it's a duplicate key, count it as skipped + if (String(e2).includes("UNIQUE") || String(e2).includes("duplicate")) { + // Already handled by onConflictDoNothing, shouldn't happen + inserted++; + } else { + console.error(` Error inserting "${val.name}":`, e2); + errors++; + } + } + } + } + + if ((i + BATCH_SIZE) % 200 === 0 || i + BATCH_SIZE >= newEntries.length) { + console.log( + ` Progress: ${Math.min(i + BATCH_SIZE, newEntries.length)}/${newEntries.length} (${inserted} inserted, ${errors} errors)`, + ); + } + } + + // 6. Summary + const totalDiseases = await db + .select({ count: sql`COUNT(*)` }) + .from(diseases) + .get(); + const totalPlants = await db + .select({ count: sql`COUNT(*)` }) + .from(plants) + .get(); + + console.log(`\n📊 Results:`); + console.log(` Inserted: ${inserted}`); + console.log(` Errors: ${errors}`); + console.log(` Skipped (already existed): ${skipped.length}`); + console.log(`\n📊 Database now has:`); + console.log(` ${totalPlants?.count ?? 0} plants`); + console.log(` ${totalDiseases?.count ?? 0} diseases`); + + closeDb(); +} + +main().catch((err) => { + console.error("❌ Failed:", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fill-brave-images-v2.ts b/apps/web/scripts/fill-brave-images-v2.ts new file mode 100644 index 0000000..4526560 --- /dev/null +++ b/apps/web/scripts/fill-brave-images-v2.ts @@ -0,0 +1,414 @@ +#!/usr/bin/env node +/** + * fill-brave-images-v2.ts — Brave Image Search for remaining disease images. + * + * Prioritizes by severity (critical → high → moderate → low). + * Runs at 1 request/sec (Brave free tier rate limit). + * Updates Turso DB directly with found images. + * When current key is exhausted, prompts for next key. + * Falls back to duckduckgo-images-api when all keys are spent. + * + * Usage: + * cd apps/web && npx tsx scripts/fill-brave-images-v2.ts + * + * Pass additional API keys as args: + * npx tsx scripts/fill-brave-images-v2.ts KEY2 KEY3 + */ + +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; + +// Load env +const envPath = resolve(__dirname, "../.env.development"); +try { + const env = readFileSync(envPath, "utf-8"); + for (const line of env.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.slice(0, eqIdx).trim(); + const val = trimmed.slice(eqIdx + 1).trim(); + if (!process.env[key]) process.env[key] = val; + } + } + } +} catch {} + +// Also try .env.local for BRAVE_API_KEY +try { + const envLocal = readFileSync(resolve(__dirname, "../.env.local"), "utf-8"); + for (const line of envLocal.split("\n")) { + const trimmed = line.trim(); + if (trimmed.startsWith("BRAVE_API_KEY=")) { + const val = trimmed.slice("BRAVE_API_KEY=".length).trim(); + if (!process.env.BRAVE_API_KEY) process.env.BRAVE_API_KEY = val; + } + } +} catch {} + +import { getDb, closeDb } from "../src/lib/db/index"; +import { diseases } from "../src/lib/db/schema"; +import { createClient } from "@libsql/client"; +import { sql } from "drizzle-orm"; + +interface DiseaseRow { + id: string; + name: string; + scientificName: string; + severity: string; + plantId: string; +} + +// ─── Config ────────────────────────────────────────────────────────────────── + +const BRAVE_DELAY = 1100; // ms between calls (1 req/sec) +const DB_FLUSH_BATCH = 50; +const MAX_PER_KEY = 1800; // Leave 200 buffer of the 2000/mo limit +const STATE_FILE = resolve(__dirname, ".brave-progress.json"); + +let currentKeyIndex = 0; +let braveKeys: string[] = []; +let callsThisKey = 0; +let totalFound = 0; +// totalSkipped tracking removed — not needed for v2 + +// ─── State persistence ─────────────────────────────────────────────────────── + +interface RunState { + processedIds: string[]; + currentKeyIndex: number; + callsThisKey: number; + totalFound: number; +} + +function loadState(): RunState | null { + try { + return JSON.parse(readFileSync(STATE_FILE, "utf-8")); + } catch { + return null; + } +} + +function saveState(processedIds: string[]) { + writeFileSync( + STATE_FILE, + JSON.stringify( + { + processedIds, + currentKeyIndex, + callsThisKey, + totalFound, + }, + null, + 2, + ), + "utf-8", + ); +} + +// ─── Brave API ─────────────────────────────────────────────────────────────── + +async function braveImageSearch(query: string): Promise { + const key = braveKeys[currentKeyIndex]; + if (!key) return null; + + const url = new URL("https://api.search.brave.com/res/v1/images/search"); + url.searchParams.set("q", query); + url.searchParams.set("count", "3"); + + for (let attempt = 0; attempt < 3; attempt++) { + try { + const res = await fetch(url.toString(), { + headers: { "X-Subscription-Token": key, Accept: "application/json" }, + }); + + if (res.status === 429) { + console.log("\n [RATE LIMITED] Key " + (currentKeyIndex + 1) + " exhausted!"); + return "RATE_LIMITED"; + } + if (!res.ok) return null; + + callsThisKey++; + const data = (await res.json()) as { + results?: Array<{ url: string; thumbnail?: { src?: string } }>; + }; + const results = data?.results ?? []; + if (results.length === 0) return null; + + // Prefer non-stock images + for (const r of results) { + const src = r.thumbnail?.src ?? r.url; + if (src && !/(dreamstime|shutterstock|alamy|istock|123rf)/i.test(src)) { + return src; + } + } + return results[0].thumbnail?.src ?? results[0].url; + } catch { + await new Promise((r) => setTimeout(r, 2000)); + } + } + return null; +} + +// ─── DuckDuckGo fallback ──────────────────────────────────────────────────── + +async function ddgFallbackSearch(query: string): Promise { + try { + // Try to use duckduckgo-images-api if installed + const ddg = await import("duckduckgo-images-api").catch(() => null); + if (ddg) { + const results = await ddg.image_search({ query, moderate: true }); + if (results && results.length > 0) { + for (const r of results) { + if (r.image && !/(dreamstime|shutterstock|alamy|istock|123rf)/i.test(r.image)) { + return r.image; + } + } + return results[0].image || null; + } + } + } catch { + // duckduckgo-images-api not installed + } + return null; +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + console.log("\n🔍 Brave Disease Image Filler v2\n"); + + // Parse keys from args + env + const argsKeys = process.argv.slice(2).filter((a) => !a.startsWith("-")); + const envKey = process.env.BRAVE_API_KEY; + braveKeys = [envKey, ...argsKeys].filter(Boolean) as string[]; + braveKeys = [...new Set(braveKeys)]; // dedup + + if (braveKeys.length === 0) { + console.log("❌ No Brave API keys found."); + console.log(" Set BRAVE_API_KEY in .env.local or pass as argument.\n"); + process.exit(1); + } + console.log(`🔑 ${braveKeys.length} Brave API key(s) available\n`); + + // Load state + const state = loadState(); + if (state) { + currentKeyIndex = state.currentKeyIndex; + callsThisKey = state.callsThisKey; + totalFound = state.totalFound; + console.log( + `📋 Resuming from previous run (${state.processedIds.length} processed, ${totalFound} found)\n`, + ); + } + + // Get diseases from DB + const db = getDb(); + const allDiseases = (await db + .select({ + id: diseases.id, + name: diseases.name, + scientificName: diseases.scientificName, + severity: diseases.severity, + plantId: diseases.plantId, + }) + .from(diseases) + .where(sql`(image_url IS NULL OR image_url = '')`) + .all()) as DiseaseRow[]; + + console.log(`📋 ${allDiseases.length} diseases need images\n`); + + if (allDiseases.length === 0) { + console.log("✅ All diseases already have images!\n"); + closeDb(); + return; + } + + // Sort by severity priority + const severityOrder = { critical: 0, high: 1, moderate: 2, low: 3 }; + allDiseases.sort( + (a, b) => + (severityOrder[a.severity as keyof typeof severityOrder] || 99) - + (severityOrder[b.severity as keyof typeof severityOrder] || 99), + ); + + // Filter out already-processed from state + const processedSet = new Set(state?.processedIds || []); + const pending = allDiseases.filter((d) => !processedSet.has(d.id)); + + console.log( + `📊 Prioritization: critical=${allDiseases.filter((d) => d.severity === "critical" && !processedSet.has(d.id)).length}, high=${allDiseases.filter((d) => d.severity === "high" && !processedSet.has(d.id)).length}, moderate=${allDiseases.filter((d) => d.severity === "moderate" && !processedSet.has(d.id)).length}, low=${allDiseases.filter((d) => d.severity === "low" && !processedSet.has(d.id)).length}\n`, + ); + + if (pending.length === 0) { + console.log("✅ All remaining diseases already attempted\n"); + closeDb(); + return; + } + + const raw = createClient({ + url: process.env.DATABASE_URL!, + authToken: process.env.DATABASE_TOKEN!, + }); + + let updates: Array<{ id: string; url: string }> = []; + const processedIds: string[] = state?.processedIds || []; + let found = totalFound; + let ddgMode = false; + + for (let i = 0; i < pending.length; i++) { + const d = pending[i]; + + // Check if current key needs rotating + if (!ddgMode && callsThisKey >= MAX_PER_KEY) { + if (currentKeyIndex < braveKeys.length - 1) { + currentKeyIndex++; + callsThisKey = 0; + console.log(`\n 🔄 Rotating to key ${currentKeyIndex + 1}/${braveKeys.length}\n`); + } else { + console.log( + `\n ⚠️ All ${braveKeys.length} Brave keys exhausted. Switching to DuckDuckGo fallback.\n`, + ); + ddgMode = true; + // Install duckduckgo-images-api if not available + try { + await import("duckduckgo-images-api"); + } catch { + console.log(" Installing duckduckgo-images-api..."); + const { execSync } = await import("child_process"); + execSync("npm install duckduckgo-images-api", { + cwd: resolve(__dirname, ".."), + stdio: "pipe", + }); + console.log(" Done.\n"); + } + } + } + + // Build search query + const plantName = d.plantId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); + const query = `${d.name} ${d.scientificName} ${plantName} plant disease`; + const sev = d.severity.padEnd(8); + + process.stdout.write( + ` [${String(i + 1).padStart(4)}/${pending.length}] [${sev}] ${d.name.substring(0, 40).padEnd(42)} `, + ); + + let url: string | null = null; + + if (ddgMode) { + url = await ddgFallbackSearch(query); + if (!url) { + // Try a simpler query + url = await ddgFallbackSearch(`${d.name} disease`); + } + } else { + url = await braveImageSearch(query); + if (url === "RATE_LIMITED") { + // Key exhausted mid-query, try next + if (currentKeyIndex < braveKeys.length - 1) { + currentKeyIndex++; + callsThisKey = 0; + console.log("\n 🔄 Rotating key..."); + url = await braveImageSearch(query); + } else { + console.log("\n ⚠️ All keys exhausted mid-batch!"); + ddgMode = true; + url = await ddgFallbackSearch(query); + } + } + } + + if (url) { + updates.push({ id: d.id, url }); + found++; + processedIds.push(d.id); + console.log("✅"); + } else { + processedIds.push(d.id); // Mark as attempted even if not found + console.log("❌"); + } + + // Flush to DB + if (updates.length >= DB_FLUSH_BATCH) { + await raw.batch( + updates.map((u) => ({ + sql: "UPDATE diseases SET image_url = ?, updated_at = datetime() WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + updates = []; + } + + // Save state every 50 + if ((i + 1) % 50 === 0) { + saveState(processedIds); + } + + // Rate limit (even for DDG to be polite) + await new Promise((r) => setTimeout(r, ddgMode ? 500 : BRAVE_DELAY)); + } + + // Final flush + if (updates.length > 0) { + await raw.batch( + updates.map((u) => ({ + sql: "UPDATE diseases SET image_url = ?, updated_at = datetime() WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + } + + saveState(processedIds); + raw.close(); + + // Final report + const finalList = await db + .select({ id: diseases.id, name: diseases.name, imageUrl: diseases.imageUrl }) + .from(diseases) + .all(); + const w = finalList.filter((d) => d.imageUrl); + const wo = finalList.filter((d) => !d.imageUrl); + + console.log(`\n${"═".repeat(50)}`); + console.log(`📊 BRAVE IMAGE SEARCH COMPLETE`); + console.log(`${"═".repeat(50)}`); + console.log(` Processed: ${pending.length}`); + console.log(` Found this run: ${found - totalFound}`); + console.log(` Total with images: ${w.length}/${finalList.length}`); + console.log(` Still missing: ${wo.length}`); + console.log(` Brave keys used: ${currentKeyIndex + 1}`); + console.log(` Calls on current key: ${callsThisKey}`); + console.log(` DuckDuckGo mode: ${ddgMode}`); + + if (wo.length > 0) { + const rp = resolve(__dirname, ".disease-image-review-needed.md"); + let report = "# Disease Images - Still Missing\n\n"; + report += `Generated: ${new Date().toISOString()}\n\n`; + report += `## Summary\n\n`; + report += `- Total: ${finalList.length}\n`; + report += `- With images: ${w.length}\n`; + report += `- Still missing: ${wo.length}\n\n`; + report += `## Missing Diseases\n\n`; + for (const d of wo) { + report += `- ${d.name} (\`${d.id}\`)\n`; + } + writeFileSync(rp, report, "utf-8"); + console.log(`\n📝 Report: ${rp}`); + } else { + console.log("\n✅ ALL diseases now have images!"); + } + + closeDb(); + console.log("\n"); +} + +main().catch((err) => { + console.error("\n❌", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fill-brave-images.ts b/apps/web/scripts/fill-brave-images.ts new file mode 100644 index 0000000..4a621e1 --- /dev/null +++ b/apps/web/scripts/fill-brave-images.ts @@ -0,0 +1,152 @@ +#!/usr/bin/env node +/** + * fill-brave-images.ts — Brave-only pass for remaining disease images. + * + * Runs at 1 request/sec (Brave rate limit). + * Updates diseases.json and Turso DB. + * + * Usage: cd apps/web && npx tsx scripts/fill-brave-images.ts + */ + +import dotenv from "dotenv"; dotenv.config({ path: resolve(__dirname, "../.env.local") }); +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; +import { createClient } from "@libsql/client"; +import { closeDb } from "../src/lib/db/index"; + +const DISEASES_JSON = resolve(__dirname, "../src/data/diseases.json"); +const BRAVE_KEY = process.env.BRAVE_API_KEY ?? ""; + +interface DiseaseSeed { + id: string; + plantId: string; + name: string; + scientificName: string; + imageUrl?: string; + [key: string]: unknown; +} + +function load(): DiseaseSeed[] { + return JSON.parse(readFileSync(DISEASES_JSON, "utf-8")) as DiseaseSeed[]; +} + +async function searchBraveImage(query: string): Promise { + const url = new URL("https://api.search.brave.com/res/v1/images/search"); + url.searchParams.set("q", query); + url.searchParams.set("count", "3"); + + for (let attempt = 0; attempt < 3; attempt++) { + try { + const res = await fetch(url.toString(), { + headers: { "X-Subscription-Token": BRAVE_KEY, Accept: "application/json" }, + }); + if (res.status === 429) { + await new Promise((r) => setTimeout(r, 5000 * 2 ** attempt)); + continue; + } + if (!res.ok) return null; + const data = (await res.json()) as { + results?: Array<{ url: string; thumbnail?: { src?: string } }>; + }; + const results = data?.results ?? []; + if (results.length === 0) return null; + + // Prefer non-stock direct-looking images + for (const r of results) { + const src = r.thumbnail?.src ?? r.url; + if (src && !/(dreamstime|shutterstock|alamy|istock|123rf)/i.test(src)) return src; + } + return results[0].thumbnail?.src ?? results[0].url; + } catch { + await new Promise((r) => setTimeout(r, 2000)); + } + } + return null; +} + +async function main() { + console.log("\n🔍 Brave Image Search — remaining disease images\n"); + + if (!BRAVE_KEY) { + console.log("❌ No BRAVE_API_KEY in .env.local\n"); + process.exit(1); + } + + const diseases = load(); + const pending = diseases.filter((d) => !d.imageUrl); + console.log(`📋 ${pending.length} diseases need images\n`); + + let found = 0; + + for (let i = 0; i < pending.length; i++) { + const d = pending[i]; + const plant = diseases.find((p) => p.id === d.plantId); + const plantName = plant?.name ?? d.plantId; + const query = `${d.name} ${plantName} plant disease symptom`; + + process.stdout.write(` [${String(i + 1).padStart(2, " ")}/${pending.length}] ${d.name.padEnd(35)} `); + + const url = await searchBraveImage(query); + if (url) { + d.imageUrl = url; + found++; + console.log(`✅`); + } else { + console.log(`❌`); + } + + // 1 req/sec rate limit + await new Promise((r) => setTimeout(r, 1100)); + } + + // Write updated JSON + writeFileSync(DISEASES_JSON, JSON.stringify(diseases, null, 2) + "\n", "utf-8"); + console.log(`\n✅ diseases.json updated: ${found}/${pending.length} images found\n`); + + // Update DB + try { + const dbUrl = process.env.DATABASE_URL; + const dbToken = process.env.DATABASE_TOKEN; + if (dbUrl && dbToken) { + const raw = createClient({ url: dbUrl, authToken: dbToken }); + const updates = pending.filter((d) => d.imageUrl); + for (let i = 0; i < updates.length; i += 50) { + await raw.batch( + updates.slice(i, i + 50).map((d) => ({ + sql: "UPDATE diseases SET image_url = ? WHERE id = ?", + args: [d.imageUrl!, d.id], + })), + "write", + ); + } + raw.close(); + console.log(`✅ Turso DB updated: ${updates.length} rows`); + } else { + console.log("⏭️ Skipping DB — no DATABASE_URL/TOKEN"); + } + } catch (err) { + console.log(` ⚠️ DB: ${err instanceof Error ? err.message : err}`); + } + + // Summary + const finalDiseases = JSON.parse(readFileSync(DISEASES_JSON, "utf-8")) as DiseaseSeed[]; + const stillMissing = finalDiseases.filter((d) => !d.imageUrl); + console.log(`\n${"═".repeat(50)}`); + console.log(`📊 FINAL: ${finalDiseases.length} total`); + console.log(` With images: ${finalDiseases.length - stillMissing.length}`); + console.log(` Still missing: ${stillMissing.length}`); + if (stillMissing.length > 0) { + console.log(`\nStill need human curation:`); + for (const d of stillMissing) { + console.log(` ❌ ${d.name} (${d.id})`); + } + } + console.log(`${"═".repeat(50)}\n`); + + closeDb(); +} + +main().catch((err) => { + console.error("\n❌ Fatal:", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fill-ddg-images.ts b/apps/web/scripts/fill-ddg-images.ts new file mode 100644 index 0000000..0981589 --- /dev/null +++ b/apps/web/scripts/fill-ddg-images.ts @@ -0,0 +1,266 @@ +#!/usr/bin/env node +/** + * fill-ddg-images.ts — DuckDuckGo Image Search for remaining disease images. + * + * No API key needed. Searches DuckDuckGo Images API for each disease + * without an image and updates the Turso DB. + * + * Prioritizes by severity (critical → high → moderate → low). + * Runs at 1 request/sec to be polite to DuckDuckGo. + * Resumable via state file (scripts/.ddg-progress.json). + * + * Usage: + * cd apps/web && npx tsx scripts/fill-ddg-images.ts + */ + +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; + +// Load .env.development for DB creds +const envPath = resolve(__dirname, "../.env.development"); +try { + const env = readFileSync(envPath, "utf-8"); + for (const line of env.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.slice(0, eqIdx).trim(); + const val = trimmed.slice(eqIdx + 1).trim(); + if (!process.env[key]) process.env[key] = val; + } + } + } +} catch {} + +import { getDb, closeDb } from "../src/lib/db/index"; +import { diseases } from "../src/lib/db/schema"; +import { createClient } from "@libsql/client"; +import { sql } from "drizzle-orm"; + +// DuckDuckGo +import { imageSearch } from "@mudbill/duckduckgo-images-api"; + +interface DiseaseRow { + id: string; + name: string; + scientificName: string; + severity: string; + plantId: string; +} + +// ─── Config ────────────────────────────────────────────────────────────────── + +const POLITE_DELAY = 1100; // ms between calls +const DB_FLUSH_BATCH = 50; +const STATE_FILE = resolve(__dirname, ".ddg-progress.json"); + +interface RunState { + processedIds: string[]; + totalFound: number; +} + +function loadState(): RunState | null { + try { + return JSON.parse(readFileSync(STATE_FILE, "utf-8")); + } catch { + return null; + } +} + +function saveState(processedIds: string[], totalFound: number) { + writeFileSync(STATE_FILE, JSON.stringify({ processedIds, totalFound }, null, 2), "utf-8"); +} + +// ─── DuckDuckGo Search ─────────────────────────────────────────────────────── + +async function searchImage(query: string): Promise { + try { + const results = await imageSearch({ query, safe: true, iterations: 1, retries: 2 }); + if (!results || results.length === 0) return null; + + // Prefer non-stock images + for (const r of results) { + if (r.image && !/(dreamstime|shutterstock|alamy|istock|123rf)/i.test(r.image)) { + return r.image; + } + } + return results[0].image || results[0].thumbnail || null; + } catch { + // DuckDuckGo may block or timeout; silently skip + return null; + } +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + console.log("\n🦆 DuckDuckGo Disease Image Filler\n"); + + const db = getDb(); + + // Load state + const state = loadState(); + const processedSet = new Set(state?.processedIds || []); + const totalFoundPrev = state?.totalFound ?? 0; + + // Get all diseases that still need images + const allDiseases = (await db + .select({ + id: diseases.id, + name: diseases.name, + scientificName: diseases.scientificName, + severity: diseases.severity, + plantId: diseases.plantId, + }) + .from(diseases) + .where(sql`(image_url IS NULL OR image_url = '')`) + .all()) as DiseaseRow[]; + + console.log(`📋 ${allDiseases.length} diseases need images\n`); + + if (allDiseases.length === 0) { + console.log("✅ All diseases already have images!\n"); + closeDb(); + return; + } + + // Sort by severity: critical > high > moderate > low + const severityOrder: Record = { critical: 0, high: 1, moderate: 2, low: 3 }; + allDiseases.sort((a, b) => (severityOrder[a.severity] ?? 99) - (severityOrder[b.severity] ?? 99)); + + // Filter out already-processed + const pending = allDiseases.filter((d) => !processedSet.has(d.id)); + + console.log( + `📊 Remaining: critical=${allDiseases.filter((d) => d.severity === "critical" && !processedSet.has(d.id)).length}, ` + + `high=${allDiseases.filter((d) => d.severity === "high" && !processedSet.has(d.id)).length}, ` + + `moderate=${allDiseases.filter((d) => d.severity === "moderate" && !processedSet.has(d.id)).length}, ` + + `low=${allDiseases.filter((d) => d.severity === "low" && !processedSet.has(d.id)).length}\n`, + ); + + if (pending.length === 0) { + console.log("✅ All remaining diseases already attempted\n"); + closeDb(); + return; + } + + const raw = createClient({ + url: process.env.DATABASE_URL!, + authToken: process.env.DATABASE_TOKEN!, + }); + + const processedIds: string[] = state?.processedIds ?? []; + let found = totalFoundPrev; + let updates: Array<{ id: string; url: string }> = []; + + for (let i = 0; i < pending.length; i++) { + const d = pending[i]; + const sev = d.severity.padEnd(8); + + // Build search query — "[disease] on [plant]" phrasing for better specificity + const plantName = d.plantId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); + const query1 = `${d.name} on ${plantName} plant disease`; + const query2 = `${d.scientificName || d.name} on ${plantName} disease`; + const query3 = `${d.name} plant disease ${plantName}`; + + process.stdout.write( + ` [${String(i + 1).padStart(4)}/${pending.length}] [${sev}] ${d.name.substring(0, 42).padEnd(44)} `, + ); + + // Try queries in order until we get a result + let url: string | null = null; + for (const q of [query1, query2, query3]) { + url = await searchImage(q); + if (url) break; + } + + if (url) { + updates.push({ id: d.id, url }); + found++; + processedIds.push(d.id); + console.log("✅"); + } else { + processedIds.push(d.id); + console.log("❌"); + } + + // Flush to DB in batches + if (updates.length >= DB_FLUSH_BATCH) { + await raw.batch( + updates.map((u) => ({ + sql: "UPDATE diseases SET image_url = ?, updated_at = datetime() WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + updates = []; + } + + // Save state every 50 + if ((i + 1) % 50 === 0) { + saveState(processedIds, found); + } + + // Be polite — 1 req/sec + await new Promise((r) => setTimeout(r, POLITE_DELAY)); + } + + // Final flush + if (updates.length > 0) { + await raw.batch( + updates.map((u) => ({ + sql: "UPDATE diseases SET image_url = ?, updated_at = datetime() WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + } + + saveState(processedIds, found); + raw.close(); + + // Final report + const finalList = await db + .select({ id: diseases.id, name: diseases.name, imageUrl: diseases.imageUrl }) + .from(diseases) + .all(); + const w = finalList.filter((d) => d.imageUrl); + const wo = finalList.filter((d) => !d.imageUrl); + + console.log(`\n${"═".repeat(50)}`); + console.log(`🦆 DUCKDUCKGO SEARCH COMPLETE`); + console.log(`${"═".repeat(50)}`); + console.log(` Processed: ${pending.length}`); + console.log(` Found this run: ${found - totalFoundPrev}`); + console.log(` Total with images: ${w.length}/${finalList.length}`); + console.log(` Still missing: ${wo.length}`); + + if (wo.length > 0) { + const reportPath = resolve(__dirname, ".ddg-image-review-needed.md"); + let report = "# Disease Images - Still Missing (DDG)\n\n"; + report += `Generated: ${new Date().toISOString()}\n\n`; + report += `## Summary\n\n`; + report += `- Total: ${finalList.length}\n`; + report += `- With images: ${w.length}\n`; + report += `- Still missing: ${wo.length}\n\n`; + report += `## Missing Diseases\n\n`; + for (const d of wo) { + report += `- ${d.name} (\`${d.id}\`)\n`; + } + writeFileSync(reportPath, report, "utf-8"); + console.log(`\n📝 Missing report: ${reportPath}`); + } else { + console.log("\n✅ ALL diseases now have images!"); + } + + closeDb(); + console.log(); +} + +main().catch((err) => { + console.error("\n❌ Fatal:", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fill-plant-images-v2.ts b/apps/web/scripts/fill-plant-images-v2.ts new file mode 100644 index 0000000..9c84b21 --- /dev/null +++ b/apps/web/scripts/fill-plant-images-v2.ts @@ -0,0 +1,301 @@ +#!/usr/bin/env node +/** + * fill-plant-images-v2.ts — Batch Wikipedia image fetch for remaining plants. + * + * Phase 1: Query 50 scientific names at a time via pageimages. + * Phase 2: Query 50 common names at a time. + * Phase 3: Search individually for stragglers. + * + * Usage: cd apps/web && npx tsx scripts/fill-plant-images-v2.ts + */ + +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; + +// Load env +const envPath = resolve(__dirname, "../.env.development"); +try { + const env = readFileSync(envPath, "utf-8"); + for (const line of env.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.slice(0, eqIdx).trim(); + const val = trimmed.slice(eqIdx + 1).trim(); + if (!process.env[key]) { + process.env[key] = val; + } + } + } + } +} catch (e) {} + +import { getDb, closeDb } from "../src/lib/db/index"; +import { plants } from "../src/lib/db/schema"; +import { createClient } from "@libsql/client"; +import { sql } from "drizzle-orm"; + +const API = "https://en.wikipedia.org/w/api.php"; +const UA = "PlantHealthKB/1.0"; +const BATCH = 50; + +interface PlantRow { + id: string; + commonName: string; + scientificName: string; +} + +function clean(s: string): string { + return s + .replace(/[xX]/g, "x") + .replace(/\s*spp\.?\s*/gi, "") + .replace(/[.\u00d7']/g, "") + .trim(); +} + +async function fetchThumbs(titles: string[]): Promise> { + if (titles.length === 0) { + return new Map(); + } + const p = new URLSearchParams({ + action: "query", + titles: titles.join("|"), + prop: "pageimages", + pithumbsize: "400", + redirects: "1", + format: "json", + }); + for (let a = 0; a < 3; a++) { + try { + const r = await fetch(API + "?" + p.toString(), { + headers: { "User-Agent": UA }, + }); + if (r.status === 429) { + await new Promise((rr) => setTimeout(rr, 5000 * Math.pow(2, a))); + continue; + } + if (!r.ok) { + return new Map(); + } + const d = (await r.json()) as any; + const pages = d?.query?.pages; + if (!pages) { + return new Map(); + } + const m = new Map(); + for (const [, pg] of Object.entries(pages)) { + const p2 = pg as any; + if (!p2.missing && p2.thumbnail?.source) { + m.set(p2.title.toLowerCase(), p2.thumbnail.source); + } + } + return m; + } catch (e) { + await new Promise((rr) => setTimeout(rr, 2000)); + } + } + return new Map(); +} + +async function searchOne(query: string): Promise { + const p = new URLSearchParams({ + action: "query", + generator: "search", + gsrsearch: query, + gsrlimit: "3", + prop: "pageimages", + pithumbsize: "400", + format: "json", + }); + for (let a = 0; a < 3; a++) { + try { + const r = await fetch(API + "?" + p.toString(), { + headers: { "User-Agent": UA }, + }); + if (r.status === 429) { + await new Promise((rr) => setTimeout(rr, 5000 * Math.pow(2, a))); + continue; + } + if (!r.ok) { + return null; + } + const d = (await r.json()) as any; + const pages = d?.query?.pages; + if (!pages) { + return null; + } + for (const [, pg] of Object.entries(pages)) { + const p2 = pg as any; + if (p2.thumbnail?.source) { + return p2.thumbnail.source; + } + } + return null; + } catch (e) { + await new Promise((rr) => setTimeout(rr, 2000)); + } + } + return null; +} + +async function batchPhase( + plants: PlantRow[], + titleFn: (p: PlantRow) => string, + label: string, + dbClient: any, +): Promise { + const remaining: PlantRow[] = []; + const updates: Array<{ id: string; url: string }> = []; + + for (let i = 0; i < plants.length; i += BATCH) { + const chunk = plants.slice(i, i + BATCH); + const titles = chunk.map(titleFn).filter((t) => t.length > 2); + console.log( + " [" + + label + + "] " + + (i + 1) + + "-" + + Math.min(i + BATCH, plants.length) + + "/" + + plants.length + + " ", + ); + const imageMap = await fetchThumbs(titles); + let n = 0; + for (const pl of chunk) { + const t = titleFn(pl).toLowerCase(); + const img = imageMap.get(t); + if (img) { + updates.push({ id: pl.id, url: img }); + n++; + } else { + remaining.push(pl); + } + } + console.log(" found: " + n); + if (updates.length >= 100) { + await dbClient.batch( + updates.map((u) => ({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + updates.length = 0; + } + await new Promise((r) => setTimeout(r, 1500)); + } + + if (updates.length > 0) { + await dbClient.batch( + updates.map((u) => ({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + } + + return remaining; +} + +async function main() { + console.log("\nPlant Image Filler v2\n"); + const db = getDb(); + const allPlants = (await db + .select({ + id: plants.id, + commonName: plants.commonName, + scientificName: plants.scientificName, + }) + .from(plants) + .where(sql`(image_url IS NULL OR image_url = '')`) + .all()) as PlantRow[]; + + console.log("Plants needing images: " + allPlants.length + "\n"); + if (allPlants.length === 0) { + console.log("All plants have images!\n"); + closeDb(); + return; + } + + const raw = createClient({ + url: process.env.DATABASE_URL!, + authToken: process.env.DATABASE_TOKEN!, + }); + let found = 0; + + // Phase 1: Scientific name + console.log("--- Phase 1: Scientific names ---\n"); + let remaining = await batchPhase(allPlants, (p) => clean(p.scientificName), "sci", raw); + + // Phase 2: Common name + if (remaining.length > 0) { + console.log("\n--- Phase 2: Common names (" + remaining.length + ") ---\n"); + remaining = await batchPhase(remaining, (p) => p.commonName, "common", raw); + } + + // Phase 3: Search + if (remaining.length > 0) { + console.log("\n--- Phase 3: Search (" + remaining.length + ") ---\n"); + for (let i = 0; i < remaining.length; i++) { + const pl = remaining[i]; + const q = clean(pl.scientificName) + " " + pl.commonName; + console.log(" [" + (i + 1) + "/" + remaining.length + "] " + pl.commonName); + const img = await searchOne(q); + if (img) { + await raw.execute({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [img, pl.id], + }); + found++; + console.log(" OK"); + } else { + console.log(" MISS"); + } + await new Promise((r) => setTimeout(r, 500)); + } + } + + raw.close(); + + // Report + const finalList = await db + .select({ + id: plants.id, + commonName: plants.commonName, + imageUrl: plants.imageUrl, + }) + .from(plants) + .all(); + const w = finalList.filter((p) => p.imageUrl); + const wo = finalList.filter((p) => !p.imageUrl); + + console.log("\n" + "=".repeat(50)); + console.log("FINAL: " + finalList.length + " plants"); + console.log(" With images: " + w.length); + console.log(" Missing: " + wo.length); + + if (wo.length > 0) { + const rp = resolve(__dirname, ".plant-image-review-needed.md"); + let report = "# Plant Images - Still Missing\n\n"; + report += "Generated: " + new Date().toISOString() + "\n\n"; + report += "## Missing (" + wo.length + ")\n\n"; + for (const p of wo) { + report += "- " + p.commonName + " (" + p.id + ")\n"; + } + writeFileSync(rp, report, "utf-8"); + console.log("Report: " + rp); + } else { + console.log("\nALL PLANTS HAVE IMAGES!"); + } + + closeDb(); +} + +main().catch((err: any) => { + console.error("Error:", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fill-plant-images.ts b/apps/web/scripts/fill-plant-images.ts new file mode 100644 index 0000000..c080f5a --- /dev/null +++ b/apps/web/scripts/fill-plant-images.ts @@ -0,0 +1,308 @@ +#!/usr/bin/env node +/** + * fill-plant-images.ts — Fetch plant images from Wikipedia for plants missing them. + * + * Uses the Wikipedia API to search for the plant's scientific name + * and grab the page thumbnail. + * + * Usage: cd apps/web && npx tsx scripts/fill-plant-images.ts + */ + +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; + +// Load env +const envPath = resolve(__dirname, "../.env.development"); +try { + const env = readFileSync(envPath, "utf-8"); + for (const line of env.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.slice(0, eqIdx).trim(); + const val = trimmed.slice(eqIdx + 1).trim(); + if (!process.env[key]) process.env[key] = val; + } + } + } +} catch {} + +import { getDb, closeDb } from "../src/lib/db/index"; +import { plants } from "../src/lib/db/schema"; +import { createClient } from "@libsql/client"; +import { sql } from "drizzle-orm"; + +const WIKI_API = "https://en.wikipedia.org/w/api.php"; +const UA = "PlantHealthKB/1.0 (plant-images)"; +const DELAY_MS = 500; +const BATCH_SIZE = 50; + +/** Direct page lookup by title — more reliable for known scientific names. */ +async function directPageLookup(title: string): Promise { + const params = new URLSearchParams({ + action: "query", + titles: title, + prop: "pageimages", + pithumbsize: "400", + format: "json", + origin: "*", + }); + + for (let attempt = 0; attempt < 3; attempt++) { + try { + const res = await fetch(`${WIKI_API}?${params}`, { + headers: { "User-Agent": UA }, + }); + if (res.status === 429) { + await new Promise((r) => setTimeout(r, 3000 * 2 ** attempt)); + continue; + } + if (!res.ok) return null; + const data = (await res.json()) as { + query?: { pages?: Record }; + }; + const pages = data?.query?.pages; + if (!pages) return null; + for (const [, p] of Object.entries(pages)) { + if (!p.missing && p.thumbnail?.source) return p.thumbnail.source; + } + return null; + } catch { + await new Promise((r) => setTimeout(r, 2000)); + } + } + return null; +} + +async function main() { + console.log("\n🌿 Fetching plant images from Wikipedia\n"); + + const db = getDb(); + const allPlants = await db + .select({ id: plants.id, commonName: plants.commonName, scientificName: plants.scientificName }) + .from(plants) + .where(sql`(image_url IS NULL OR image_url = '')`) + .all(); + + console.log(`📋 ${allPlants.length} plants need images\n`); + + if (allPlants.length === 0) { + console.log("✅ All plants already have images!\n"); + closeDb(); + return; + } + + const rawClient = createClient({ + url: process.env.DATABASE_URL!, + authToken: process.env.DATABASE_TOKEN!, + }); + + let found = 0; + const updates: { id: string; url: string }[] = []; + + // Phase 1: Try direct page lookup by scientific name (most accurate) + console.log("─── Phase 1: Direct page lookup ───\n"); + + for (let i = 0; i < allPlants.length; i++) { + const plant = allPlants[i]; + const sciName = plant.scientificName + .replace(/[×'"]/g, "") + .replace(/\s*spp\.?\s*/i, "") + .trim(); + + process.stdout.write( + ` [${String(i + 1).padStart(3)}/${allPlants.length}] ${plant.commonName.padEnd(30)} `, + ); + + let url: string | null = null; + + // Try scientific name first + if (sciName && sciName !== "Unknown" && sciName !== "Various") { + url = await directPageLookup(sciName); + } + + // Try common name if scientific name didn't work + if (!url) { + url = await directPageLookup(plant.commonName); + } + + // Try genus name + if (!url && sciName) { + const genus = sciName.split(/\s+/)[0]; + if (genus && genus.length > 3) { + url = await directPageLookup(genus); + } + } + + if (url) { + updates.push({ id: plant.id, url }); + found++; + process.stdout.write("✅\n"); + } else { + process.stdout.write("⏭️\n"); + } + + // Flush to DB in batches + if (updates.length >= BATCH_SIZE) { + await rawClient.batch( + updates.map((u) => ({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + updates.length = 0; + } + + await new Promise((r) => setTimeout(r, DELAY_MS)); + } + + // Flush remaining + if (updates.length > 0) { + await rawClient.batch( + updates.map((u) => ({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [u.url, u.id], + })), + "write", + ); + console.log(` → Flushed ${updates.length} to DB`); + updates.length = 0; + } + + console.log(`\n✅ Phase 1 done: ${found}/${allPlants.length} plants got images\n`); + + // Phase 2: Try remaining via search API + const stillMissing = await db + .select({ id: plants.id, commonName: plants.commonName, scientificName: plants.scientificName }) + .from(plants) + .where(sql`(image_url IS NULL OR image_url = '')`) + .all(); + + if (stillMissing.length > 0) { + console.log(`─── Phase 2: Search API for ${stillMissing.length} remaining ───\n`); + + for (let i = 0; i < stillMissing.length; i++) { + const plant = stillMissing[i]; + const sciName = plant.scientificName.replace(/[×'"]/g, "").trim(); + + process.stdout.write( + ` [${String(i + 1).padStart(3)}/${stillMissing.length}] ${plant.commonName.padEnd(30)} `, + ); + + // Search with scientific name + const searchTerm = `${sciName} ${plant.commonName}`; + const params = new URLSearchParams({ + action: "query", + list: "search", + srsearch: searchTerm, + srlimit: "3", + format: "json", + origin: "*", + }); + + let url: string | null = null; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const res = await fetch(`${WIKI_API}?${params}`, { + headers: { "User-Agent": UA }, + }); + if (res.status === 429) { + await new Promise((r) => setTimeout(r, 3000 * 2 ** attempt)); + continue; + } + if (!res.ok) break; + const data = (await res.json()) as { + query?: { search?: Array<{ title: string; pageid: number }> }; + }; + const hits = data?.query?.search ?? []; + if (hits.length === 0) break; + + // Get thumbnail for first result + for (const hit of hits) { + const pageParams = new URLSearchParams({ + action: "query", + pageids: String(hit.pageid), + prop: "pageimages", + pithumbsize: "400", + format: "json", + origin: "*", + }); + const pageRes = await fetch(`${WIKI_API}?${pageParams}`, { + headers: { "User-Agent": UA }, + }); + if (!pageRes.ok) continue; + const pageData = (await pageRes.json()) as { + query?: { pages?: Record }; + }; + const pages = pageData?.query?.pages; + if (!pages) continue; + for (const [, p] of Object.entries(pages)) { + if (p.thumbnail?.source) { + url = p.thumbnail.source; + break; + } + } + if (url) break; + } + break; + } catch { + await new Promise((r) => setTimeout(r, 2000)); + } + } + + if (url) { + await rawClient.execute({ + sql: "UPDATE plants SET image_url = ?, updated_at = datetime('now') WHERE id = ?", + args: [url, plant.id], + }); + found++; + process.stdout.write("✅\n"); + } else { + process.stdout.write("❌\n"); + } + + await new Promise((r) => setTimeout(r, DELAY_MS)); + } + } + + // Final count + const final = await db + .select({ id: plants.id, commonName: plants.commonName, imageUrl: plants.imageUrl }) + .from(plants) + .all(); + const withImg = final.filter((p) => p.imageUrl); + const withoutImg = final.filter((p) => !p.imageUrl); + + console.log(`\n${"═".repeat(50)}`); + console.log(`📊 FINAL: ${final.length} plants`); + console.log(` With images: ${withImg.length}`); + console.log(` Missing images: ${withoutImg.length}`); + + if (withoutImg.length > 0) { + console.log(`\n📝 Plants still needing images:`); + withoutImg.forEach((p) => console.log(` ❌ ${p.id}: ${p.commonName}`)); + // Save to file for reference + const reportPath = resolve(__dirname, ".plant-image-review-needed.md"); + let report = "# Plant Images — Still Missing\n\n"; + report += `Generated: ${new Date().toISOString()}\n\n`; + report += `## 🚫 Plants without images (${withoutImg.length})\n\n`; + for (const p of withoutImg) { + report += `- **${p.commonName}** (\`${p.id}\`)\n`; + } + writeFileSync(reportPath, report, "utf-8"); + console.log(` 📝 Review report: ${reportPath}`); + } else { + console.log("\n✅ All plants now have images!"); + } + + rawClient.close(); + closeDb(); +} + +main().catch((err) => { + console.error("\n❌", err); + process.exit(1); +}); diff --git a/apps/web/scripts/fix-classifications.ts b/apps/web/scripts/fix-classifications.ts new file mode 100644 index 0000000..8988d4b --- /dev/null +++ b/apps/web/scripts/fix-classifications.ts @@ -0,0 +1,212 @@ +#!/usr/bin/env node +/** + * fix-classifications.ts — Fix misclassified diseases in the DB. + * + * Fixes: + * 1. Diseases named with viral indicators (mosaic, mottle, ringspot, virus, etc.) + * that are incorrectly tagged as "fungal" + * 2. Other suspicious patterns + * + * Usage: cd apps/web && npx tsx scripts/fix-classifications.ts + */ + +import { readFileSync } from "fs"; +import { resolve } from "path"; + +// Manually load .env.development +const envPath = resolve(__dirname, "../.env.development"); +try { + const env = readFileSync(envPath, "utf-8"); + for (const line of env.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.slice(0, eqIdx).trim(); + const val = trimmed.slice(eqIdx + 1).trim(); + if (!process.env[key]) process.env[key] = val; + } + } + } +} catch {} + +import { getDb, closeDb } from "../src/lib/db/index"; +import { diseases } from "../src/lib/db/schema"; +import { createClient } from "@libsql/client"; + +type AgentType = "fungal" | "bacterial" | "viral" | "environmental"; + +interface FixRule { + test: (name: string) => boolean; + correctAgent: AgentType; + reason: string; +} + +const FIX_RULES: FixRule[] = [ + // Diseases explicitly named as "virus" or "viral" + { + test: (name) => /\b(virus|viral|viroid)\b/i.test(name), + correctAgent: "viral", + reason: "Name explicitly indicates viral disease", + }, + // Potexvirus, carlavirus, etc. + { + test: (name) => + /\b(virus\b|potex|carla|tobamo|poty|cucumo|ilar|nepo|tymovirus|geminivir|tom bushy stunt)\b/i.test( + name, + ), + correctAgent: "viral", + reason: "Recognized virus genus in name", + }, + // "Mosaic" diseases (typically viral) + { + test: (name) => /\bmosaic\b/i.test(name), + correctAgent: "viral", + reason: "Mosaic symptoms are typically caused by viruses", + }, + // "Mottle" diseases (typically viral) + { + test: (name) => /\bmottle\b/i.test(name), + correctAgent: "viral", + reason: "Mottle symptoms are typically caused by viruses", + }, + // "Ringspot" diseases (typically viral) + { + test: (name) => /\bringspot\b/i.test(name), + correctAgent: "viral", + reason: "Ringspot symptoms are typically caused by viruses", + }, + // "Leaf curl" (many are viral) + { + test: (name) => /\bleaf curl\b|\bleafroll\b|\bleaf-roll\b/i.test(name), + correctAgent: "viral", + reason: "Leaf curl/roll diseases are often viral", + }, + // "Rosette" (often viral or phytoplasma) + { + test: (name) => /\brosette\b/i.test(name), + correctAgent: "viral", + reason: "Rosette diseases are typically viral or phytoplasma", + }, + // "Yellows" (often phytoplasma/viral) + { + test: (name) => /\byellows\b/i.test(name) && !/\bpeach\b/i.test(name), + correctAgent: "viral", + reason: "Yellows diseases are typically phytoplasma or viral", + }, + // "Stunt" / "Dwarf" (often viral) + { + test: (name) => /\b(stunt|dwarf(ism)?)\b/i.test(name), + correctAgent: "viral", + reason: "Stunting/dwarfing diseases are often viral", + }, + // Explicit bacterial in name + { + test: (name) => + /\bbacterial\b|\bbacterium\b|\berwinia\b|\bpseudomonas\b|\bxanthomonas\b|\bralstonia\b|\bclavibacter\b|\bstreptomyces\b|\bagrobacterium\b/i.test( + name, + ), + correctAgent: "bacterial", + reason: "Name indicates bacterial disease", + }, + // Environmental/abiotic indicators + { + test: (name) => + /\b(deficiency|abiotic|environmental|injury|damage|stress|sunscald|sunburn|chilling|freeze|frost|wind|hail|nutrient|toxicity|snow\s+(mold|scald)|winter\s+(injury|rot|kill))\b/i.test( + name, + ), + correctAgent: "environmental", + reason: "Name indicates abiotic/environmental cause", + }, +]; + +async function main() { + console.log("🔍 Fixing disease classifications\n"); + const db = getDb(); + const allDiseases = await db + .select({ id: diseases.id, name: diseases.name, causalAgentType: diseases.causalAgentType }) + .from(diseases) + .all(); + console.log(`📋 ${allDiseases.length} total diseases\n`); + + const rawClient = createClient({ + url: process.env.DATABASE_URL!, + authToken: process.env.DATABASE_TOKEN!, + }); + + const updates: { id: string; newAgent: AgentType; rule: FixRule; oldAgent: string }[] = []; + + for (const d of allDiseases) { + for (const rule of FIX_RULES) { + if (rule.test(d.name)) { + if (d.causalAgentType !== rule.correctAgent) { + updates.push({ + id: d.id, + newAgent: rule.correctAgent, + rule, + oldAgent: d.causalAgentType, + }); + } + break; // First matching rule wins + } + } + } + + console.log(`Found ${updates.length} diseases needing reclassification:\n`); + + // Group by correction type + const grouped: Record = {}; + for (const u of updates) { + const key = `${u.oldAgent}→${u.newAgent}`; + if (!grouped[key]) grouped[key] = { from: u.oldAgent, to: u.newAgent, items: [] }; + grouped[key].items.push(` ${u.id}`); + } + + for (const [, g] of Object.entries(grouped)) { + console.log(`${g.from} → ${g.to} (${g.items.length} diseases):`); + g.items.slice(0, 10).forEach((l) => console.log(l)); + if (g.items.length > 10) console.log(` ... and ${g.items.length - 10} more`); + console.log(); + } + + // Apply updates + if (updates.length === 0) { + console.log("✅ No corrections needed"); + } else { + console.log(`Applying ${updates.length} corrections...\n`); + + // Batch update in groups of 50 + for (let i = 0; i < updates.length; i += 50) { + const batch = updates.slice(i, i + 50); + await rawClient.batch( + batch.map((u) => ({ + sql: "UPDATE diseases SET causal_agent_type = ?, updated_at = datetime('now') WHERE id = ?", + args: [u.newAgent, u.id], + })), + "write", + ); + process.stdout.write(` ${Math.min(i + 50, updates.length)}/${updates.length}\n`); + } + + console.log(`\n✅ ${updates.length} diseases reclassified`); + } + + // Print summary stats + const after = await db.select({ causalAgentType: diseases.causalAgentType }).from(diseases).all(); + const counts: Record = {}; + after.forEach((d) => { + counts[d.causalAgentType] = (counts[d.causalAgentType] || 0) + 1; + }); + console.log("\n📊 Updated distribution:"); + for (const [type, count] of Object.entries(counts).sort()) { + console.log(` ${type}: ${count}`); + } + + rawClient.close(); + closeDb(); +} + +main().catch((err) => { + console.error("\n❌", err); + process.exit(1); +}); diff --git a/apps/web/scripts/generate-full-kb.ts b/apps/web/scripts/generate-full-kb.ts index 8c69685..a53f948 100644 --- a/apps/web/scripts/generate-full-kb.ts +++ b/apps/web/scripts/generate-full-kb.ts @@ -20,7 +20,7 @@ import { getDb, closeDb } from "../src/lib/db/index"; import { diseases, plants } from "../src/lib/db/schema"; import PLANTS from "./plant-list"; import { GENERIC_TEMPLATES, getTemplatesForFamily, slugify } from "./disease-templates"; -import type { Disease, CausalAgentType, Severity } from "../src/lib/types"; +import type { CausalAgentType, Prevalence, Severity } from "../src/lib/types"; interface DiseaseEntry { id: string; @@ -35,6 +35,7 @@ interface DiseaseEntry { prevention: string[]; lookalikeIds: string[]; severity: Severity; + prevalence: Prevalence; sourceUrl: string; } @@ -92,7 +93,6 @@ async function main() { // Determine how many diseases we need for this plant const targetMin = 15; // minimum diseases per plant - const targetMax = 45; // maximum diseases per plant // Get family-specific templates const familyTemplates = getTemplatesForFamily(plant.fam); @@ -128,6 +128,7 @@ async function main() { prevention: tmpl.prevention, lookalikeIds: [], severity: tmpl.severity, + prevalence: tmpl.severity === "critical" ? "uncommon" : "common", sourceUrl: "https://pddc.wisc.edu/ (UW-Madison PDDC extension factsheets)", }); } @@ -202,7 +203,7 @@ async function main() { for (let i = 0; i < toInsert.length; i += BATCH) { const chunk = toInsert.slice(i, i + BATCH); const stmts = chunk.map((d) => ({ - sql: `INSERT OR IGNORE INTO diseases (id, plant_id, name, scientific_name, causal_agent_type, description, symptoms, causes, treatment, prevention, lookalike_ids, severity, source_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + sql: `INSERT OR IGNORE INTO diseases (id, plant_id, name, scientific_name, causal_agent_type, description, symptoms, causes, treatment, prevention, lookalike_ids, severity, prevalence, source_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ d.id, d.plantId, @@ -216,6 +217,7 @@ async function main() { JSON.stringify(d.prevention), JSON.stringify(d.lookalikeIds), d.severity, + d.prevalence ?? "uncommon", d.sourceUrl, ], })); diff --git a/apps/web/scripts/seed-existing.ts b/apps/web/scripts/seed-existing.ts index 7d0a958..3c8a9b4 100644 --- a/apps/web/scripts/seed-existing.ts +++ b/apps/web/scripts/seed-existing.ts @@ -68,6 +68,7 @@ async function main() { prevention: d.prevention, lookalikeIds: d.lookalikeDiseaseIds, severity: d.severity, + prevalence: d.prevalence ?? "uncommon", sourceUrl: "", }) .onConflictDoNothing(); diff --git a/apps/web/src/app/api/identify/route.ts b/apps/web/src/app/api/identify/route.ts index 9452a25..b16e0f2 100644 --- a/apps/web/src/app/api/identify/route.ts +++ b/apps/web/src/app/api/identify/route.ts @@ -31,13 +31,13 @@ const IMAGENET_MEAN = [0.485, 0.456, 0.406] as const; const IMAGENET_STD = [0.229, 0.224, 0.225] as const; /** Model input size */ -const MODEL_SIZE = 224; +const MODEL_SIZE = 160; // ─── Server-side image preprocessing ───────────────────────────────────────── /** * Load an uploaded image and preprocess it into a Float32Array tensor - * with shape [3, 224, 224] (NCHW without batch dim) using ImageNet normalization. + * with shape [3, 160, 160] (NCHW without batch dim) using ImageNet normalization. * * @param imageId - The image ID from the upload endpoint * @returns Float32Array tensor ready for inference diff --git a/apps/web/src/app/api/plants/[id]/view/route.ts b/apps/web/src/app/api/plants/[id]/view/route.ts new file mode 100644 index 0000000..12e9317 --- /dev/null +++ b/apps/web/src/app/api/plants/[id]/view/route.ts @@ -0,0 +1,38 @@ +/** + * POST /api/plants/[id]/view + * + * Increments the view count for a plant in the plant_views table. + * Called client-side from the plant detail page via a tiny tracker component. + */ + +import { NextResponse } from "next/server"; +import { eq, sql } from "drizzle-orm"; +import { getDb } from "@/lib/db/index"; +import { plantViews } from "@/lib/db/schema"; + +export async function POST(_request: Request, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + + if (!id) { + return NextResponse.json({ error: "Missing plant id" }, { status: 400 }); + } + + try { + const db = getDb(); + + // Upsert: increment view_count if row exists, otherwise insert with count 1 + await db + .insert(plantViews) + .values({ plantId: id, viewCount: 1 }) + .onConflictDoUpdate({ + target: plantViews.plantId, + set: { viewCount: sql`${plantViews.viewCount} + 1` }, + }); + + return NextResponse.json({ ok: true }); + } catch (err) { + console.error("[View] Failed to record view for", id, err); + // Swallow errors — tracking failure shouldn't break the page + return NextResponse.json({ ok: true }); + } +} diff --git a/apps/web/src/app/browse/BrowseContent.test.tsx b/apps/web/src/app/browse/BrowseContent.test.tsx index 856980b..290f177 100644 --- a/apps/web/src/app/browse/BrowseContent.test.tsx +++ b/apps/web/src/app/browse/BrowseContent.test.tsx @@ -37,6 +37,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Solanum lycopersicum", family: "Solanaceae", category: "vegetable", + imageUrl: "https://example.com/tomato.jpg", diseaseCount: 15, }, { @@ -45,6 +46,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Ocimum basilicum", family: "Lamiaceae", category: "herb", + imageUrl: "https://example.com/basil.jpg", diseaseCount: 3, }, { @@ -53,6 +55,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Rosa spp.", family: "Rosaceae", category: "flower", + imageUrl: "https://example.com/rose.jpg", diseaseCount: 7, }, { @@ -61,6 +64,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Monstera deliciosa", family: "Araceae", category: "houseplant", + imageUrl: "https://example.com/monstera.jpg", diseaseCount: 5, }, { @@ -69,6 +73,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Dracaena trifasciata", family: "Asparagaceae", category: "houseplant", + imageUrl: "https://example.com/snake-plant.jpg", diseaseCount: 2, }, { @@ -77,6 +82,7 @@ const MOCK_PLANTS: PlantCardData[] = [ scientificName: "Capsicum annuum", family: "Solanaceae", category: "vegetable", + imageUrl: "https://example.com/pepper.jpg", diseaseCount: 9, }, ]; diff --git a/apps/web/src/app/browse/BrowseContent.tsx b/apps/web/src/app/browse/BrowseContent.tsx index 299065b..f569128 100644 --- a/apps/web/src/app/browse/BrowseContent.tsx +++ b/apps/web/src/app/browse/BrowseContent.tsx @@ -7,6 +7,14 @@ import EmptyState from "@/components/EmptyState"; import { PLANT_CATEGORIES } from "@/lib/constants"; import type { PlantCardData } from "@/components/PlantCard"; +type SortKey = "name" | "recent" | "popular"; + +const SORT_OPTIONS: { value: SortKey; label: string }[] = [ + { value: "name", label: "Name (A-Z)" }, + { value: "recent", label: "Recently Updated" }, + { value: "popular", label: "Most Popular" }, +]; + interface BrowseContentProps { allPlants: PlantCardData[]; } @@ -24,6 +32,7 @@ export default function BrowseContent({ allPlants }: BrowseContentProps) { const [searchQuery, setSearchQuery] = useState(initialSearch); const [activeCategory, setActiveCategory] = useState("all"); + const [sortKey, setSortKey] = useState("name"); const filteredPlants = useMemo(() => { let result = allPlants; @@ -42,8 +51,22 @@ export default function BrowseContent({ allPlants }: BrowseContentProps) { ); } - return result; - }, [activeCategory, searchQuery, allPlants]); + // Sort + const sorted = [...result]; + if (sortKey === "recent") { + sorted.sort((a, b) => { + const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0; + const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0; + return bTime - aTime; // newest first + }); + } else if (sortKey === "popular") { + sorted.sort((a, b) => (b.viewCount ?? 0) - (a.viewCount ?? 0)); + } else { + sorted.sort((a, b) => a.commonName.localeCompare(b.commonName)); + } + + return sorted; + }, [activeCategory, searchQuery, allPlants, sortKey]); return (
@@ -55,44 +78,14 @@ export default function BrowseContent({ allPlants }: BrowseContentProps) {

- {/* Search bar */} -
- -
- - setSearchQuery(e.target.value)} - className="w-full rounded-xl border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-900 pl-10 pr-4 py-3 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-leaf-green-500 focus:border-leaf-green-500 transition-all shadow-sm" - /> -
- {searchQuery && ( - - )} + setSearchQuery(e.target.value)} + className="w-full rounded-xl border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-900 pl-10 pr-4 py-3 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-leaf-green-500 focus:border-leaf-green-500 transition-all shadow-sm" + /> +
+ {searchQuery && ( + + )} + + + {/* Sort dropdown */} +
+ + + +
{/* Category filter chips */} diff --git a/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx b/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx index a72b8a3..358d349 100644 --- a/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx +++ b/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx @@ -1,7 +1,7 @@ "use client"; -import { useState, useCallback } from "react"; -import type { Disease, CausalAgentType, Severity } from "@/lib/types"; +import { useState, useCallback, useMemo } from "react"; +import type { Disease, CausalAgentType, Prevalence, Severity } from "@/lib/types"; import ImageLightbox from "@/components/ImageLightbox"; // ─── Severity badge ─── @@ -79,6 +79,7 @@ function DiseaseCard({ )}
+
@@ -207,16 +208,205 @@ function DiseaseCard({ ); } +// ─── Prevalence badge ─── + +function PrevalenceBadge({ prevalence }: { prevalence: Prevalence }) { + const icons: Record = { + common: "📊", + uncommon: "📋", + rare: "📌", + }; + const colors: Record = { + common: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300", + uncommon: "bg-zinc-100 text-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-300", + rare: "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300", + }; + + return ( + + {icons[prevalence]} {prevalence.charAt(0).toUpperCase() + prevalence.slice(1)} + + ); +} + +// ─── Sort / Search controls ─── + +const SEVERITY_RANK: Record = { + critical: 4, + high: 3, + moderate: 2, + low: 1, +}; + +const PREVALENCE_RANK: Record = { + common: 3, + uncommon: 2, + rare: 1, +}; + +type SortField = "prevalence" | "danger"; + +function SearchSortBar({ + searchQuery, + onSearchChange, + sortField, + onSortFieldChange, + sortOrder, + onSortOrderToggle, + resultCount, +}: { + searchQuery: string; + onSearchChange: (q: string) => void; + sortField: SortField; + onSortFieldChange: (f: SortField) => void; + sortOrder: "asc" | "desc"; + onSortOrderToggle: () => void; + resultCount: number; +}) { + return ( +
+ {/* Search */} +
+ + onSearchChange(e.target.value)} + placeholder="Search diseases by name…" + className="w-full rounded-lg border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 py-2 pl-10 pr-3 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-leaf-green-500 focus:border-leaf-green-500 transition-colors" + aria-label="Search diseases" + /> +
+ + {/* Sort controls */} +
+ Sort by: +
+ + +
+ + {/* Direction toggle */} + + + + {resultCount} {resultCount === 1 ? "result" : "results"} + +
+
+ ); +} + // ─── Client component wrapper ─── export default function DiseaseCards({ diseases }: { diseases: Disease[] }) { const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxIndex, setLightboxIndex] = useState(0); + const [searchQuery, setSearchQuery] = useState(""); + const [sortField, setSortField] = useState("danger"); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); - // Build list of images from diseases that have imageUrls - const images = diseases - .filter((d) => d.imageUrl) - .map((d) => ({ src: d.imageUrl!, alt: `${d.name} symptoms` })); + // ── Filtered + sorted diseases ── + + const processed = useMemo(() => { + // Filter + let result = diseases; + const trimmed = searchQuery.trim().toLowerCase(); + if (trimmed) { + result = result.filter( + (d) => + d.name.toLowerCase().includes(trimmed) || + d.scientificName.toLowerCase().includes(trimmed), + ); + } + + // Sort + const sorted = [...result].sort((a, b) => { + let cmp: number; + if (sortField === "danger") { + cmp = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]; + } else { + cmp = PREVALENCE_RANK[a.prevalence] - PREVALENCE_RANK[b.prevalence]; + } + return sortOrder === "desc" ? -cmp : cmp; + }); + + return sorted; + }, [diseases, searchQuery, sortField, sortOrder]); + + // Build list of images from processed diseases that have imageUrls + const images = useMemo( + () => + processed + .filter((d) => d.imageUrl) + .map((d) => ({ src: d.imageUrl!, alt: `${d.name} symptoms` })), + [processed], + ); const handleImageClick = useCallback( (disease: Disease) => { @@ -229,15 +419,40 @@ export default function DiseaseCards({ diseases }: { diseases: Disease[] }) { const handleClose = useCallback(() => setLightboxOpen(false), []); + const handleSortOrderToggle = useCallback(() => { + setSortOrder((prev) => (prev === "desc" ? "asc" : "desc")); + }, []); + if (diseases.length === 0) return null; return ( <> -
- {diseases.map((disease) => ( - - ))} -
+ + + {processed.length > 0 ? ( +
+ {processed.map((disease) => ( + + ))} +
+ ) : ( +
+ +

+ No diseases match “{searchQuery}”. +

+
+ )} {lightboxOpen && images.length > 0 && ( diff --git a/apps/web/src/app/browse/[plantId]/page.tsx b/apps/web/src/app/browse/[plantId]/page.tsx index c1a2b29..8730101 100644 --- a/apps/web/src/app/browse/[plantId]/page.tsx +++ b/apps/web/src/app/browse/[plantId]/page.tsx @@ -1,9 +1,11 @@ +import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; import type { Metadata } from "next"; import { getPlantWithDiseases } from "@/lib/api/diseases-db"; -import { getEmojiForCategory, getPlantDescription } from "@/lib/display-helpers"; +import { getPlantDescription } from "@/lib/display-helpers"; import DiseaseCards from "./DiseaseCards"; +import PlantViewTracker from "@/components/PlantViewTracker"; interface Props { params: Promise<{ plantId: string }>; @@ -44,7 +46,6 @@ export default async function PlantDetailPage({ params }: Props) { } const { plant, diseases } = result; - const emoji = getEmojiForCategory(plant.category); const description = getPlantDescription( plant.commonName, plant.scientificName, @@ -53,107 +54,135 @@ export default async function PlantDetailPage({ params }: Props) { ); return ( -
- {/* Breadcrumb */} - + <> + +
+ {/* Breadcrumb */} + - {/* Plant hero */} -
- {/* Emoji illustration */} -
- -
- -
-

- {plant.commonName} -

-

- {plant.scientificName} -

-

- Family: {plant.family} - {" · "} - Category: {plant.category} -

-

- {description} -

-
- - {plant.careSummary} + {/* Plant hero */} +
+ {/* Plant image */} +
+ {plant.imageUrl ? ( + {plant.commonName} + ) : ( +
+ +
+ )}
-
-
- {/* Identify disease CTA */} -
-
-
-

- 🧐 Spot a problem on your {plant.commonName.toLowerCase()}? -

-

- Upload a photo for AI-powered disease identification. +

+

+ {plant.commonName} +

+

+ {plant.scientificName}

+

+ Family: {plant.family} + {" · "} + Category: {plant.category} +

+

+ {description} +

+
+ + {plant.careSummary} +
- - 📸 Identify a Disease - +
+ + {/* Identify disease CTA */} +
+
+
+

+ 🧐 Spot a problem on your {plant.commonName.toLowerCase()}? +

+

+ Upload a photo for AI-powered disease identification. +

+
+ + 📸 Identify a Disease + +
+
+ + {/* Disease list */} +
+

+ Known Diseases +

+

+ {diseases.length === 0 + ? "No diseases currently documented for this plant." + : `${diseases.length} ${diseases.length === 1 ? "disease" : "diseases"} documented for ${plant.commonName}.`} +

+ + {diseases.length > 0 ? ( + + ) : ( +
+ +

+ Disease data for {plant.commonName} is being researched and will be added soon. +

+
+ )}
- - {/* Disease list */} -
-

- Known Diseases -

-

- {diseases.length === 0 - ? "No diseases currently documented for this plant." - : `${diseases.length} ${diseases.length === 1 ? "disease" : "diseases"} documented for ${plant.commonName}.`} -

- - {diseases.length > 0 ? ( - - ) : ( -
- -

- Disease data for {plant.commonName} is being researched and will be added soon. -

-
- )} -
-
+ ); } diff --git a/apps/web/src/components/PlantCard.test.tsx b/apps/web/src/components/PlantCard.test.tsx index dd6bde9..e8a8003 100644 --- a/apps/web/src/components/PlantCard.test.tsx +++ b/apps/web/src/components/PlantCard.test.tsx @@ -10,6 +10,7 @@ describe("PlantCard", () => { scientificName: "Solanum lycopersicum", family: "Solanaceae", category: "vegetable", + imageUrl: "https://example.com/tomato.jpg", diseaseCount: 2, }; @@ -18,10 +19,18 @@ describe("PlantCard", () => { expect(screen.getByText("Tomato")).toBeInTheDocument(); }); - it("renders plant emoji (generated from category)", () => { + it("renders plant image", () => { render(); - // Vegetable category → 🥬 emoji - expect(screen.getByText("🥬")).toBeInTheDocument(); + const img = screen.getByRole("img") as HTMLImageElement; + expect(img).toHaveAttribute("src", expect.stringContaining("tomato.jpg")); + expect(img).toHaveAttribute("alt", "Tomato"); + }); + + it("renders fallback SVG when no image URL", () => { + const noImagePlant = { ...mockPlant, imageUrl: "" }; + render(); + // Should render SVG fallback instead of image + expect(screen.queryByRole("img")).not.toBeInTheDocument(); }); it("renders plant family", () => { diff --git a/apps/web/src/components/PlantCard.tsx b/apps/web/src/components/PlantCard.tsx index 5d9b1fe..9562daa 100644 --- a/apps/web/src/components/PlantCard.tsx +++ b/apps/web/src/components/PlantCard.tsx @@ -1,6 +1,5 @@ -import React from "react"; +import Image from "next/image"; import Link from "next/link"; -import { getEmojiForCategory } from "@/lib/display-helpers"; export interface PlantCardData { id: string; @@ -8,7 +7,10 @@ export interface PlantCardData { scientificName: string; family: string; category: string; + imageUrl: string; diseaseCount: number; + updatedAt?: string; + viewCount?: number; } interface PlantCardProps { @@ -17,26 +19,45 @@ interface PlantCardProps { } /** - * Plant card showing emoji, name, family, and optional disease count. + * Plant card showing image, name, family, and optional disease count. * Used on the homepage featured section and browse grid. */ export default function PlantCard({ plant, showDiseaseCount = true }: PlantCardProps) { - const emoji = getEmojiForCategory(plant.category); - return ( - {/* Emoji illustration area */} -
- + {/* Plant image */} +
+ {plant.imageUrl ? ( + {plant.commonName} + ) : ( +
+ +
+ )}
diff --git a/apps/web/src/components/PlantViewTracker.tsx b/apps/web/src/components/PlantViewTracker.tsx new file mode 100644 index 0000000..68b852c --- /dev/null +++ b/apps/web/src/components/PlantViewTracker.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { useEffect } from "react"; + +/** + * Tracks a plant page view by POSTing to the view-count API. + * Renders nothing — purely a side-effect component. + */ +export default function PlantViewTracker({ plantId }: { plantId: string }) { + useEffect(() => { + const controller = new AbortController(); + + fetch(`/api/plants/${encodeURIComponent(plantId)}/view`, { + method: "POST", + signal: controller.signal, + // Keepalive so the request completes even if the user navigates away quickly + keepalive: true, + }).catch(() => { + // Silently ignore tracking failures + }); + + return () => controller.abort(); + }, [plantId]); + + return null; +} diff --git a/apps/web/src/data/diseases.json b/apps/web/src/data/diseases.json index 56ffc1f..e0ca71c 100644 --- a/apps/web/src/data/diseases.json +++ b/apps/web/src/data/diseases.json @@ -14,13 +14,13 @@ "Wilting of severely affected branches" ], "causes": [ - "Warm temperatures (80-85\u00b0F) combined with high humidity or frequent overhead watering", + "Warm temperatures (80-85°F) combined with high humidity or frequent overhead watering", "Fungal spores overwintering in infected plant debris or soil", "Close plant spacing reducing air circulation", "Nutrient deficiencies, particularly potassium deficiency weakening plant resistance" ], "treatment": [ - "Remove and destroy all severely infected leaves immediately \u2014 do not compost", + "Remove and destroy all severely infected leaves immediately — do not compost", "Apply copper-based fungicide or chlorothalonil spray every 7-10 days, covering both upper and lower leaf surfaces", "Improve air circulation by pruning lower leaves and thinning dense foliage", "Mulch around base with 2-3 inches of straw to prevent soil-borne spore splash", @@ -38,7 +38,10 @@ "late-blight", "bacterial-leaf-spot-tomato" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Late_blight_on_potato_leaf_2.jpg/960px-Late_blight_on_potato_leaf_2.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "late-blight", @@ -55,16 +58,16 @@ "Grayish-white mold on stems and petioles" ], "causes": [ - "Cool temperatures (60-70\u00b0F) with prolonged leaf wetness from rain or dew", + "Cool temperatures (60-70°F) with prolonged leaf wetness from rain or dew", "Spores blown from infected potato plants or neighboring gardens", "Overhead irrigation extending leaf wetness periods", "Planting susceptible varieties in areas with history of late blight" ], "treatment": [ - "Immediately remove and destroy all infected plant material \u2014 bag it to prevent spore spread", + "Immediately remove and destroy all infected plant material — bag it to prevent spore spread", "Apply mancozeb or copper-based fungicide as emergency treatment every 5-7 days", "Reduce humidity around plants by improving air circulation", - "Harvest any unaffected fruit immediately and cure indoors at 85\u00b0F", + "Harvest any unaffected fruit immediately and cure indoors at 85°F", "In severe outbreaks, consider destroying the entire crop to prevent spread" ], "prevention": [ @@ -78,7 +81,10 @@ "early-blight", "septoria-leaf-spot" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Patates.jpg/960px-Patates.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "septoria-leaf-spot", @@ -95,20 +101,20 @@ "Reduced fruit set and smaller fruit size due to leaf loss" ], "causes": [ - "Hot (75-85\u00b0F), humid weather with frequent rain or overhead irrigation", + "Hot (75-85°F), humid weather with frequent rain or overhead irrigation", "Fungal spores splashing from soil where infected debris overwintered", "Dense plantings that keep foliage wet and reduce air flow", "Working among wet plants and spreading spores on hands and tools" ], "treatment": [ - "Remove lower infected leaves and destroy them \u2014 start from bottom up", + "Remove lower infected leaves and destroy them — start from bottom up", "Apply copper fungicide or chlorothalonil spray every 7-14 days", "Mulch heavily (3-4 inches) around base to prevent soil splash", "Improve air circulation by staking and pruning interior branches", "Apply a broad-spectrum fungicide containing myclobutanil for severe cases" ], "prevention": [ - "Rotate crops \u2014 do not plant tomatoes or peppers in same bed for 2-3 years", + "Rotate crops — do not plant tomatoes or peppers in same bed for 2-3 years", "Use drip irrigation and avoid wetting foliage", "Space plants adequately (24-36 inches) for air circulation", "Remove all plant debris and sanitize tools at end of season", @@ -119,7 +125,10 @@ "bacterial-leaf-spot-tomato", "late-blight" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Septoria_lycopersici_malagutii_leaf_spot_on_tomato_leaf.jpg/960px-Septoria_lycopersici_malagutii_leaf_spot_on_tomato_leaf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "blossom-end-rot", @@ -143,7 +152,7 @@ ], "treatment": [ "Apply 2-3 inches of organic mulch to maintain consistent soil moisture", - "Water deeply and regularly \u2014 aim for 1-2 inches per week on a consistent schedule", + "Water deeply and regularly — aim for 1-2 inches per week on a consistent schedule", "Test soil pH and adjust to 6.2-6.8 for optimal calcium availability", "If soil test confirms low calcium, add crushed eggshells or garden lime", "Reduce high-nitrogen fertilizers and switch to balanced or high-potassium formula" @@ -151,7 +160,7 @@ "prevention": [ "Maintain consistent soil moisture with drip irrigation and mulching", "Test soil before planting and amend with lime if calcium is low", - "Avoid excessive nitrogen fertilizer \u2014 use balanced 10-10-10 or tomato-specific formula", + "Avoid excessive nitrogen fertilizer — use balanced 10-10-10 or tomato-specific formula", "Choose less susceptible varieties such as 'Celebrity' or 'Mountain Merit'", "Ensure soil pH is between 6.2 and 6.8 for optimal nutrient availability" ], @@ -160,7 +169,10 @@ "chili-blossom-end-rot", "eggplant-blossom-end-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Blossom_end_rot.JPG/960px-Blossom_end_rot.JPG", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "tomato-powdery-mildew", @@ -177,7 +189,7 @@ "Reduced fruit size and quality in severe cases" ], "causes": [ - "Warm days (70-90\u00b0F) with cool, humid nights creating ideal spore germination conditions", + "Warm days (70-90°F) with cool, humid nights creating ideal spore germination conditions", "Poor air circulation in dense plantings", "Excessive nitrogen fertilization producing succulent, susceptible growth", "Spores spreading from nearby infected plants by wind or tools" @@ -192,12 +204,15 @@ "prevention": [ "Plant resistant varieties such as 'Mountain Fresh PRS' or 'Defiant'", "Ensure adequate spacing (24-36 inches) for air circulation", - "Avoid overhead watering \u2014 water at base of plant", + "Avoid overhead watering — water at base of plant", "Apply preventive sulfur spray at first sign of mildew in the garden", - "Maintain balanced fertilization \u2014 avoid excess nitrogen" + "Maintain balanced fertilization — avoid excess nitrogen" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/6/67/Chelois.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "bacterial-leaf-spot-tomato", @@ -214,7 +229,7 @@ "Premature leaf drop reducing photosynthesis" ], "causes": [ - "Warm (75-85\u00b0F), wet weather promoting bacterial multiplication", + "Warm (75-85°F), wet weather promoting bacterial multiplication", "Contaminated seeds or infected transplants introducing bacteria", "Rain splash and overhead irrigation spreading bacteria from soil", "Infected plant debris in soil serving as overwintering reservoir" @@ -237,7 +252,10 @@ "septoria-leaf-spot", "early-blight" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/7/76/%27Cercospora_capsici.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "basil-downy-mildew", @@ -254,13 +272,13 @@ "Complete defoliation in advanced stages" ], "causes": [ - "Cool temperatures (60-75\u00b0F) with high humidity and extended leaf wetness", + "Cool temperatures (60-75°F) with high humidity and extended leaf wetness", "Spores spread by wind, rain splash, and contaminated soil", "Overhead irrigation or dew keeping leaves wet for extended periods", "Dense plantings reducing air circulation and prolonging moisture" ], "treatment": [ - "Remove and destroy all infected plants immediately \u2014 disease progresses rapidly", + "Remove and destroy all infected plants immediately — disease progresses rapidly", "Apply copper-based fungicide to remaining healthy plants as emergency treatment", "Improve air circulation by thinning plants and removing lower leaves", "Reduce humidity by spacing plants and avoiding overhead watering", @@ -271,10 +289,13 @@ "Ensure excellent air circulation with wide spacing (12-18 inches)", "Water at soil level and early in the day", "Avoid planting basil in locations with poor drainage or shade", - "Monitor weather forecasts \u2014 avoid planting when cool wet weather is predicted" + "Monitor weather forecasts — avoid planting when cool wet weather is predicted" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Grape_Rasins_plus_Zante_Currants.jpg/960px-Grape_Rasins_plus_Zante_Currants.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "basil-fusarium-wilt", @@ -292,12 +313,12 @@ ], "causes": [ "Soil-borne fungal spores (chlamydospores) persisting from previous infected crops", - "Warm soil temperatures (80-90\u00b0F) favoring fungal growth", + "Warm soil temperatures (80-90°F) favoring fungal growth", "Stress from overwatering or nutrient imbalance weakening plant defenses", "Contaminated soil, compost, or potting mix introducing the pathogen" ], "treatment": [ - "Remove and destroy infected plants \u2014 do not compost", + "Remove and destroy infected plants — do not compost", "Apply Trichoderma-based biological fungicide to soil as suppressive treatment", "Improve soil drainage and avoid overwatering", "Solarize soil by covering with clear plastic for 4-6 weeks in hot weather", @@ -313,7 +334,10 @@ "lookalikeDiseaseIds": [ "basil-root-rot" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Ocimum_basilicum_8zz.jpg/960px-Ocimum_basilicum_8zz.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "basil-cercospora-leaf-spot", @@ -330,7 +354,7 @@ "Spots may also appear on stems and petioles" ], "causes": [ - "Warm (75-85\u00b0F), humid weather with frequent rainfall or overhead irrigation", + "Warm (75-85°F), humid weather with frequent rainfall or overhead irrigation", "Fungal spores overwintering in plant debris and soil", "Rain splash dispersing spores from soil to lower leaves", "Dense plantings creating microclimates of high humidity" @@ -350,7 +374,10 @@ "Start with healthy, symptom-free transplants" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/qEe1QooFmBPBMvor3EDzfZP5vVYGlwOx7EytFqTviOQ/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9pbWFn/ZS5zbGlkZXNoYXJl/Y2RuLmNvbS9kaXNl/YXNlc29mYmFzaWxh/bmRtaW50LTE4MDYw/MjE2MzI1Ni83NS9E/aXNlYXNlcy1vZi1i/YXNpbC1hbmQtbWlu/dC0xMi0yMDQ4Lmpw/Zw", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "basil-root-rot", @@ -389,7 +416,10 @@ "lookalikeDiseaseIds": [ "basil-fusarium-wilt" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/8Qv7FgXxZpOhf9FM12OLnGoyNFHqPJfn3HPWzcgiK-I/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly9iLnRo/dW1icy5yZWRkaXRt/ZWRpYS5jb20vWHU1/dnY4Q0Z1ZDZxZXZJ/YTVqTmdfNWtRbkZq/VXA1eFRMV19YYW5Y/U2NLVS5qcGc", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "rose-black-spot", @@ -406,13 +436,13 @@ "Reduced flowering and weakened plant vigor" ], "causes": [ - "Warm (60-80\u00b0F), humid weather with frequent rain or overhead watering", + "Warm (60-80°F), humid weather with frequent rain or overhead watering", "Fungal spores splashing from fallen infected leaves onto new growth", "Poor air circulation in dense plantings or against walls", "Susceptible rose varieties lacking genetic resistance" ], "treatment": [ - "Remove and destroy all spotted leaves \u2014 do not compost", + "Remove and destroy all spotted leaves — do not compost", "Apply fungicide containing myclobutanil, tebuconazole, or triforine every 7-14 days", "Rake and destroy all fallen leaves from under bushes", "Improve air circulation by pruning interior canes", @@ -421,14 +451,17 @@ "prevention": [ "Plant resistant varieties such as 'Mister Lincoln', 'Knock Out', or 'Carefree Beauty'", "Water at base of plant using drip irrigation or soaker hose", - "Maintain clean garden \u2014 remove fallen leaves regularly", + "Maintain clean garden — remove fallen leaves regularly", "Ensure adequate spacing (3-4 feet) between bushes for air flow", "Apply preventive fungicide spray at new flush of growth each spring" ], "lookalikeDiseaseIds": [ "rose-downy-mildew" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/FhrhtzbypH95L6uCYLY8YVHAh9EohvnKUiARre4JB9g/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNS9Sb3NlLUZv/bGlhZ2Utd2l0aC1C/bGFjay1TcG90Lmpw/Zw", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "rose-powdery-mildew", @@ -436,7 +469,7 @@ "name": "Powdery Mildew", "scientificName": "Podosphaera pannosa", "causalAgentType": "fungal", - "description": "Powdery mildew on roses produces a white to gray dusty coating on leaves, buds, and stems. It is most active during warm days (70-80\u00b0F) with cool nights and moderate humidity. It distorts new growth and can prevent buds from opening.", + "description": "Powdery mildew on roses produces a white to gray dusty coating on leaves, buds, and stems. It is most active during warm days (70-80°F) with cool nights and moderate humidity. It distorts new growth and can prevent buds from opening.", "symptoms": [ "White to gray powdery coating on upper leaf surfaces", "New leaves emerge crinkled, distorted, and stunted", @@ -445,7 +478,7 @@ "Stem canes may develop powdery patches" ], "causes": [ - "Moderate temperatures (70-80\u00b0F) with high humidity but dry leaf surfaces", + "Moderate temperatures (70-80°F) with high humidity but dry leaf surfaces", "Poor air circulation in dense shrub growth", "Excessive nitrogen fertilization promoting succulent susceptible growth", "Spores overwintering on canes and bud scales" @@ -462,10 +495,13 @@ "Ensure excellent air circulation with proper spacing and pruning", "Avoid overhead watering", "Apply preventive sulfur spray at bud break in spring", - "Maintain balanced fertilization \u2014 avoid excess nitrogen" + "Maintain balanced fertilization — avoid excess nitrogen" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/3/36/Rosa_Anne_Harkness.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "rose-downy-mildew", @@ -482,7 +518,7 @@ "Severe defoliation of new growth flushes" ], "causes": [ - "Cool (50-70\u00b0F), wet weather with frequent rain or heavy dew", + "Cool (50-70°F), wet weather with frequent rain or heavy dew", "Spores spread by wind and rain splash from infected plant debris", "Overhead irrigation keeping foliage wet", "Dense plantings creating humid microclimates" @@ -492,19 +528,22 @@ "Apply fungicide containing mancozeb, chlorothalonil, or phosphite every 7-14 days", "Rake and destroy fallen leaves and pruned material", "Improve air circulation by thinning canes", - "Avoid overhead watering \u2014 water at base only" + "Avoid overhead watering — water at base only" ], "prevention": [ "Plant resistant varieties when available", "Provide excellent air circulation through spacing and pruning", "Water at soil level and early in the day", "Apply preventive fungicide in spring when new growth emerges", - "Keep garden clean \u2014 remove all fallen rose debris" + "Keep garden clean — remove all fallen rose debris" ], "lookalikeDiseaseIds": [ "rose-black-spot" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/b/b8/Downy_and_Powdery_mildew_on_grape_leaf.JPG", + "imageQuality": "good", + "prevalence": "common" }, { "id": "rose-rust", @@ -521,7 +560,7 @@ "Occasional rust lesions on stems, thorns, and leaf petioles" ], "causes": [ - "Warm (60-80\u00b0F), moist weather with frequent dew or rain", + "Warm (60-80°F), moist weather with frequent dew or rain", "Fungal spores overwintering in fallen leaves and rose canes", "Poor air circulation and dense foliage keeping leaves moist", "Wind-blown spores from neighboring infected roses" @@ -541,7 +580,10 @@ "Apply preventive fungicide spray in spring at bud break" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Rose-Rust-1.jpg/960px-Rose-Rust-1.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "monstera-root-rot", @@ -568,7 +610,7 @@ "Cut away all soft, brown, mushy roots with sterile scissors", "Repot in fresh, well-draining aroid mix (potting mix + perlite + orchid bark)", "Apply hydrogen peroxide drench (1:4 ratio with water) to disinfect remaining roots", - "Reduce watering frequency \u2014 allow top 2-3 inches to dry between waterings" + "Reduce watering frequency — allow top 2-3 inches to dry between waterings" ], "prevention": [ "Use a well-draining aroid mix with perlite, orchid bark, and pumice", @@ -580,7 +622,10 @@ "lookalikeDiseaseIds": [ "monstera-cold-damage" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Orkide_i_finstua_mot_vest.jpg/960px-Orkide_i_finstua_mot_vest.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "monstera-leaf-spot", @@ -610,14 +655,17 @@ "Wipe remaining leaves with diluted neem oil solution" ], "prevention": [ - "Avoid misting leaves \u2014 use a humidity tray or humidifier instead", + "Avoid misting leaves — use a humidity tray or humidifier instead", "Ensure good air circulation around the plant", "Water at soil level and allow soil to dry between waterings", "Wipe leaves regularly with clean damp cloth to remove dust and spores", "Quarantine new plants for 2-3 weeks before placing near existing collection" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://imgs.search.brave.com/HxJxD9jMUqCj6btc1tedJw-fWV6HTCyiE2lEXkJ0_Pk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udXN1LmVk/dS9wbGFudGhlYWx0/aC9pcG0vaW1hZ2Vz/L2FncmljdWx0dXJh/bC92ZWdldGFibGVz/L0NlcmNvc3BvcmEt/bGVhZi1zcG90LXNw/aW5hY2guanBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "monstera-cold-damage", @@ -625,7 +673,7 @@ "name": "Cold Damage / Freeze Injury", "scientificName": "Abiotic temperature injury", "causalAgentType": "environmental", - "description": "Cold damage occurs when Monstera is exposed to temperatures below 55\u00b0F (13\u00b0C) or sudden cold drafts. As a tropical plant, Monstera is sensitive to cold and can suffer leaf damage, root shock, and even death from brief cold exposure.", + "description": "Cold damage occurs when Monstera is exposed to temperatures below 55°F (13°C) or sudden cold drafts. As a tropical plant, Monstera is sensitive to cold and can suffer leaf damage, root shock, and even death from brief cold exposure.", "symptoms": [ "Dark water-soaked patches on leaves that turn black", "Leaves becoming limp and drooping rapidly", @@ -634,20 +682,20 @@ "New leaves emerging smaller or deformed" ], "causes": [ - "Room temperature dropping below 55\u00b0F (13\u00b0C)", + "Room temperature dropping below 55°F (13°C)", "Placement near cold draft from window, door, or air conditioning vent", "Cold water used for watering in winter", "Moving plant from warm room to cold environment suddenly" ], "treatment": [ - "Move plant to warm location (65-80\u00b0F) immediately", + "Move plant to warm location (65-80°F) immediately", "Do not water for a week to allow roots to recover from cold shock", "Trim away severely damaged blackened leaf tissue (not entire leaf if possible)", "Avoid fertilizing until new growth appears", "If stems are soft and mushy, take cuttings above damaged area for propagation" ], "prevention": [ - "Keep Monstera in room maintained at 65-85\u00b0F year-round", + "Keep Monstera in room maintained at 65-85°F year-round", "Avoid placing near drafty windows, doors, or AC vents", "Use room-temperature water for watering, never cold tap water", "Acclimate plants gradually when moving between locations", @@ -656,7 +704,10 @@ "lookalikeDiseaseIds": [ "monstera-root-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/ershnjIJ0rMnhFoGVHhTYhwwjSy4UVehWwlJ9ZKF0FU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wcmV2/aWV3LnJlZGQuaXQv/aXMtdGhlcmUtaG9w/ZS1mb3ItbXktbW9u/c3RlcmEtdGhhdC1n/b3QtZnJvc3QtZGFt/YWdlLWlmLWFsbC12/MC1wcmswejc1cm1w/ZDYxLmpwZz93aWR0/aD02NDAmY3JvcD1z/bWFydCZhdXRvPXdl/YnAmcz1mYTVmOTMz/MTlkOWFiOTY3ZTlm/MzdkY2VhNDY5YjQ1/ODQ4NTNiYjMw", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "monstera-spider-mites", @@ -693,7 +744,10 @@ "Avoid excessive use of nitrogen fertilizer which creates succulent susceptible tissue" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/TR9wjFRWkyus-jwsWOsygIVJbT44ddPEtvBxrYuf-RE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/c2hvcGlmeS5jb20v/cy9maWxlcy8xLzEy/ODkvMjA0MS9maWxl/cy9lYXJseS1zaWdu/cy1vZi1zcGlkZXIt/bWl0ZXMtMTRfMjA0/OHgyMDQ4LmpwZz92/PTE3MjY2NzAzNTc", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "pothos-root-rot", @@ -720,7 +774,7 @@ "Cut away all soft, brown, mushy roots with sterile scissors", "Repot in fresh well-draining potting mix with perlite", "Apply hydrogen peroxide drench to disinfect remaining roots", - "Reduce watering \u2014 allow top inch of soil to dry completely" + "Reduce watering — allow top inch of soil to dry completely" ], "prevention": [ "Water only when top 1-2 inches of soil are dry", @@ -732,7 +786,10 @@ "lookalikeDiseaseIds": [ "pothos-bacterial-soft-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Money_Plant_%28Epipremnum_aureum%29_4.jpg/960px-Money_Plant_%28Epipremnum_aureum%29_4.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "pothos-mealybugs", @@ -769,7 +826,10 @@ "Wipe leaves monthly to remove dust and early infestations" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/9ObqyNw4LWLVwrKJNnXuXPYnk9ghE5IKK-IZRTDlErw/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5mb3JpbmRvb3Iu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIxLzA1L01lYWx5/YnVncy1vbi1wb3Ro/b3MuanBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "pothos-bacterial-soft-rot", @@ -793,7 +853,7 @@ ], "treatment": [ "Cut all healthy vines above the infected area using sterile scissors", - "Discard infected plant material and soil \u2014 do not compost", + "Discard infected plant material and soil — do not compost", "Sanitize pot thoroughly with bleach solution before reuse", "Root healthy cuttings in fresh sterile water or perlite", "No chemical treatment is effective once soft rot is established" @@ -808,7 +868,10 @@ "lookalikeDiseaseIds": [ "pothos-root-rot" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/zeGhUbP2_Dt05TgOWAP5DNItLTB9EaI4bWTcKecE-5U/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24uc2RzdGF0/ZS5lZHUvc2l0ZXMv/ZGVmYXVsdC9maWxl/cy9pbmxpbmUtaW1h/Z2VzL1ctMDE3MDkt/MDEtQmFjdGVyaWFs/LVNvZnQtUm90LVN5/bXB0b21zLVN0ZW0u/anBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "snake-plant-root-rot", @@ -825,7 +888,7 @@ "Foul odor from soil and decaying tissue" ], "causes": [ - "Overwatering \u2014 the #1 cause of snake plant death", + "Overwatering — the #1 cause of snake plant death", "Pot without drainage holes", "Heavy soil mix retaining too much moisture", "Watering in winter when plant is dormant and needs minimal water" @@ -842,10 +905,13 @@ "Use cactus or succulent mix with excellent drainage", "Ensure pot has drainage holes", "Allow soil to dry completely between waterings", - "Use smaller pot relative to plant size \u2014 snake plants prefer being root-bound" + "Use smaller pot relative to plant size — snake plants prefer being root-bound" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/American_seed_and_plant_catalog_-_American_Seed_%26_Plant_Co._%28IA_CAT31340041%29.pdf/page1-960px-American_seed_and_plant_catalog_-_American_Seed_%26_Plant_Co._%28IA_CAT31340041%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "snake-plant-leaf-spot", @@ -870,19 +936,22 @@ "treatment": [ "Remove severely spotted leaves at base", "Apply copper fungicide spray to remaining foliage", - "Stop any misting practice \u2014 snake plants do not need misting", + "Stop any misting practice — snake plants do not need misting", "Wipe leaves with clean damp cloth to remove spores", "Improve air circulation around plant" ], "prevention": [ - "Water at soil level only \u2014 never wet the leaves", + "Water at soil level only — never wet the leaves", "Wipe leaves occasionally with dry cloth to remove dust", "Ensure good air circulation", "Avoid overhead watering", "Keep leaves dry and clean" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Pesticides_documentation_bulletin_%28IA_CAT11110538068%29.pdf/page1-960px-Pesticides_documentation_bulletin_%28IA_CAT11110538068%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "snake-plant-mealybugs", @@ -919,7 +988,10 @@ "Avoid over-fertilizing" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/The_Philippine_journal_of_science_%28IA_philippinejo121917phil%29.pdf/page1-960px-The_Philippine_journal_of_science_%28IA_philippinejo121917phil%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "peace-lily-bacterial-leaf-spot", @@ -949,14 +1021,17 @@ "Improve air circulation around plant" ], "prevention": [ - "Water at soil level \u2014 avoid wetting leaves", + "Water at soil level — avoid wetting leaves", "Sterilize tools between plants", "Quarantine new plants for 2-3 weeks", "Maintain good air circulation", "Avoid handling plants when foliage is wet" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/cnyXR2l1-H5EDwRDsAIxxf1aXwjzhnB2lcBzwWzLRu8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9wbGFu/dGFtZXJpY2EuY29t/L3dwLWNvbnRlbnQv/dXBsb2Fkcy8yMDI0/LzA0L0xlYWYtU3Bv/dHMtb24tUGVhY2Ut/TGlseS1QbGFudC1B/bWVyaWNhLmpwZw", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "peace-lily-root-rot", @@ -983,17 +1058,20 @@ "Cut away all rotted roots with sterile scissors", "Repot in fresh well-draining potting mix", "Apply hydrogen peroxide drench to disinfect roots", - "Reduce watering frequency \u2014 check soil moisture before each watering" + "Reduce watering frequency — check soil moisture before each watering" ], "prevention": [ "Water only when top inch of soil feels dry", "Use pot with drainage holes", "Add perlite to potting mix for drainage", "Empty saucer after watering", - "Use filtered or rain water \u2014 peace lilies are sensitive to chemicals in tap water" + "Use filtered or rain water — peace lilies are sensitive to chemicals in tap water" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Orkide_i_finstua_mot_vest.jpg/960px-Orkide_i_finstua_mot_vest.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "peace-lily-botrytis-blite", @@ -1010,7 +1088,7 @@ "Gray mold visible in humid conditions on affected tissue" ], "causes": [ - "Cool temperatures (50-65\u00b0F) with high humidity", + "Cool temperatures (50-65°F) with high humidity", "Poor air circulation around plant", "Dead flower spikes and leaves not being removed promptly", "Overwatering keeping soil and ambient humidity high" @@ -1026,11 +1104,14 @@ "Remove spent flowers and yellowing leaves promptly", "Maintain good air circulation", "Avoid overwatering", - "Keep plant at 65-80\u00b0F \u2014 avoid cool locations", + "Keep plant at 65-80°F — avoid cool locations", "Do not crowd plants together indoors" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/Z3QnqltBrd5UYNFLiZp3t0oBQSJ2loklsrVSuIekQKg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/bW9zLmNtcy5mdXR1/cmVjZG4ubmV0L2h4/bXFBNWZORXk4NnJ0/YlRqUXlmdWYuanBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "orchid-crown-rot", @@ -1060,7 +1141,7 @@ "If crown is completely destroyed, attempt keiki (baby plant) propagation from remaining roots" ], "prevention": [ - "Water by soaking roots only \u2014 never let water collect in crown", + "Water by soaking roots only — never let water collect in crown", "After watering, gently fan or blot crown area dry", "Water in morning so any trapped moisture evaporates", "Ensure good air circulation around orchid", @@ -1069,7 +1150,10 @@ "lookalikeDiseaseIds": [ "orchid-root-rot" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Orchid_Bacterial_leaf_blight_caused_by_Erwinia_sp._%2812504094455%29.jpg/960px-Orchid_Bacterial_leaf_blight_caused_by_Erwinia_sp._%2812504094455%29.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "orchid-root-rot", @@ -1080,7 +1164,7 @@ "description": "Root rot in orchids destroys the thick aerial and pot-bound roots that are essential for water and nutrient uptake. Healthy orchid roots are firm and silvery-green (dry) or bright green (after watering). Rotted roots are soft, mushy, and brown or black.", "symptoms": [ "Roots becoming soft, mushy, and brown or black", - "Roots hollowing out \u2014 skin slips off when squeezed", + "Roots hollowing out — skin slips off when squeezed", "Leaves becoming wrinkled and shriveled (dehydration from root loss)", "Plant becoming unstable in pot as roots decay", "Foul odor from pot medium" @@ -1099,16 +1183,19 @@ "Do not water for 5-7 days after repotting to allow wounds to callus" ], "prevention": [ - "Use appropriate orchid bark mix \u2014 never regular potting soil", + "Use appropriate orchid bark mix — never regular potting soil", "Water only when roots are silvery-white and medium is mostly dry", "Ensure pot has drainage holes and ventilation slots", "Use room-temperature water for watering", - "Adjust watering frequency seasonally \u2014 less in winter" + "Adjust watering frequency seasonally — less in winter" ], "lookalikeDiseaseIds": [ "orchid-crown-rot" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Phytophthora_cactorum.jpg/960px-Phytophthora_cactorum.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "orchid-bacterial-soft-rot", @@ -1119,7 +1206,7 @@ "description": "Bacterial soft rot is one of the most destructive diseases of orchids. It causes rapid softening and liquefaction of leaves, pseudobulbs, and roots. The bacteria produce pectinolytic enzymes that dissolve cell walls, turning healthy tissue into a slimy mass within days.", "symptoms": [ "Soft, watery, brown to black lesions on leaves", - "Lesions expanding rapidly \u2014 entire leaf can rot in 2-3 days", + "Lesions expanding rapidly — entire leaf can rot in 2-3 days", "Foul rotting odor from affected tissue", "Slimy bacterial ooze from cut surfaces", "Pseudobulbs becoming soft and collapsing" @@ -1131,7 +1218,7 @@ "Contaminated tools or water spreading bacteria" ], "treatment": [ - "Cut away all infected tissue with sterile knife \u2014 cut well beyond visible damage", + "Cut away all infected tissue with sterile knife — cut well beyond visible damage", "Apply hydrogen peroxide to cut surfaces to kill surface bacteria", "Dust cuts with cinnamon or apply copper bactericide", "Isolate plant immediately to prevent spread", @@ -1145,7 +1232,10 @@ "Quarantine new orchids for 3-4 weeks" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Narcissus_poeticus_subsp._radiiflorus.1658.jpg/960px-Narcissus_poeticus_subsp._radiiflorus.1658.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "orchid-leaf-spot", @@ -1170,19 +1260,22 @@ "treatment": [ "Remove severely spotted leaves at base", "Apply copper fungicide or sulfur spray to remaining leaves", - "Stop misting leaves \u2014 use humidity tray instead", + "Stop misting leaves — use humidity tray instead", "Improve air circulation with fan", "Wipe leaves with clean damp cloth" ], "prevention": [ - "Ensure good air circulation \u2014 use oscillating fan", + "Ensure good air circulation — use oscillating fan", "Avoid getting water on leaves during watering", "Maintain humidity with pebble tray or humidifier rather than misting", "Inspect leaves regularly for early spots", "Quarantine new plants" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://imgs.search.brave.com/nwqmpPs_7Fgo5qbYAWXaGJIY4oNibmfZ8QE7RasMWUU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9leHRl/bnNpb24udW1kLmVk/dS9zaXRlcy9leHRl/bnNpb24udW1kLmVk/dS9maWxlcy9zdHls/ZXMvb3B0aW1pemVk/L3B1YmxpYy8yMDIx/LTAzL2hnaWNfaG91/c2VwbGFudF9mdW5n/YWxfbGVhZl9zcG90/X29yY2hpZC1IR0lD/LTEyNDItMDMxLXNs/aWRlLmpwZz9pdG9r/PVg2RTVQUGFk", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "succulent-root-rot", @@ -1199,7 +1292,7 @@ "Foul odor from soil and decaying tissue" ], "causes": [ - "Overwatering \u2014 watering before soil has dried completely", + "Overwatering — watering before soil has dried completely", "Pot without drainage holes", "Regular potting soil instead of cactus/succulent mix", "Watering in winter when succulents are dormant" @@ -1214,12 +1307,15 @@ "prevention": [ "Use cactus/succulent mix with sharp drainage (70% mineral, 30% organic)", "Water only when soil is 100% dry and leaves feel slightly soft", - "Use pot with drainage holes \u2014 terracotta pots are ideal", + "Use pot with drainage holes — terracotta pots are ideal", "Reduce or eliminate watering in winter dormancy", "Use soak and dry method: water thoroughly, then wait until completely dry" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Peperomia_trifolia_2011-01-17.jpg/960px-Peperomia_trifolia_2011-01-17.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "succulent-mealybugs", @@ -1256,7 +1352,10 @@ "Avoid over-fertilizing which creates attractive succulent growth" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/EhBP0Sxi3YMkWaLiAi9tyn-8Xle7Gl-KgEk8I9UNK2E/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/cG90YW5kYmxvb20u/Y29tL2Nkbi9zaG9w/L2FydGljbGVzL3Vu/bmFtZWRfOC5wbmc_/dj0xNjk1OTgyMzc3/JndpZHRoPTQ4MA", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "succulent-sunburn", @@ -1269,7 +1368,7 @@ "White, tan, or brown bleached patches on leaves facing the sun", "Leaves becoming crispy and papery in severe cases", "Discoloration concentrated on top or outermost leaves", - "Permanent scarring \u2014 burned tissue does not recover", + "Permanent scarring — burned tissue does not recover", "Plant etiolation (stretching) if moved to shade after burn" ], "causes": [ @@ -1280,20 +1379,23 @@ ], "treatment": [ "Move plant to bright indirect light to prevent further damage", - "Do not remove burned leaves \u2014 they still photosynthesize and protect inner leaves", + "Do not remove burned leaves — they still photosynthesize and protect inner leaves", "Gradually acclimate to more sun over 2-3 weeks if outdoor growing is desired", "Trim only completely dead crispy leaves", - "Maintain normal watering schedule \u2014 do not overwater stressed plant" + "Maintain normal watering schedule — do not overwater stressed plant" ], "prevention": [ "Acclimate indoor succulents to outdoor sun gradually over 2-3 weeks", "Provide afternoon shade in hot summer climates", "Use shade cloth (30-50%) for intense summer sun", "Place new outdoor plants in partial shade first, then gradually increase exposure", - "Monitor plant color \u2014 healthy color indicates appropriate light level" + "Monitor plant color — healthy color indicates appropriate light level" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Peperomia_trifolia_2011-01-17.jpg/960px-Peperomia_trifolia_2011-01-17.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "pepper-blossom-end-rot", @@ -1334,7 +1436,10 @@ "chili-blossom-end-rot", "eggplant-blossom-end-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/Manual_of_laboratory_diagnosis_%28IA_manualoflaborato00boll%29.pdf/page1-500px-Manual_of_laboratory_diagnosis_%28IA_manualoflaborato00boll%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "pepper-powdery-mildew", @@ -1351,7 +1456,7 @@ "Reduced fruit set and smaller fruit" ], "causes": [ - "Warm days (70-90\u00b0F) with cool, humid nights", + "Warm days (70-90°F) with cool, humid nights", "Poor air circulation in dense plantings", "Excessive nitrogen promoting succulent growth", "Spores spreading from nearby infected plants" @@ -1371,7 +1476,10 @@ "Maintain balanced fertilization" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Loss-of-Function-in-Mlo-Orthologs-Reduces-Susceptibility-of-Pepper-and-Tomato-to-Powdery-Mildew-pone.0070723.s004.ogv/960px--Loss-of-Function-in-Mlo-Orthologs-Reduces-Susceptibility-of-Pepper-and-Tomato-to-Powdery-Mildew-pone.0070723.s004.ogv.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "pepper-bacterial-wilt", @@ -1389,12 +1497,12 @@ ], "causes": [ "Soil-borne bacteria entering through root wounds or natural openings", - "Warm soil temperatures (80-90\u00b0F) favoring bacterial multiplication", + "Warm soil temperatures (80-90°F) favoring bacterial multiplication", "Contaminated soil, compost, or transplants", "Flood irrigation or overhead watering spreading bacteria" ], "treatment": [ - "Remove and destroy infected plants \u2014 do not compost", + "Remove and destroy infected plants — do not compost", "No effective chemical treatment exists once infection is established", "Solarize soil by covering with clear plastic for 4-6 weeks in summer", "Apply biological control agents (Trichoderma, Pseudomonas fluorescens) to suppress bacteria", @@ -1408,7 +1516,10 @@ "Use resistant varieties such as 'Lamuyo' or 'Carolina Wonder'" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Bacterial_wilt_of_pepper_%289159744719%29.jpg/960px-Bacterial_wilt_of_pepper_%289159744719%29.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "cucumber-powdery-mildew", @@ -1425,7 +1536,7 @@ "Fruit developing bitter taste due to stressed plant" ], "causes": [ - "Warm days (70-85\u00b0F) with cool, humid nights", + "Warm days (70-85°F) with cool, humid nights", "Poor air circulation in dense plantings", "Excessive nitrogen fertilization", "Spores overwintering in plant debris and blown by wind" @@ -1438,7 +1549,7 @@ "Reduce nitrogen fertilization" ], "prevention": [ - "Plant resistant varieties \u2014 look for 'PM' resistance code (e.g., 'Poinsett 76', 'Marketmore 76')", + "Plant resistant varieties — look for 'PM' resistance code (e.g., 'Poinsett 76', 'Marketmore 76')", "Trellis vines for better air circulation", "Space plants 12-24 inches apart", "Avoid overhead watering", @@ -1447,7 +1558,10 @@ "lookalikeDiseaseIds": [ "cucumber-downy-mildew" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/PansyScan_%28cropped%29.jpg/960px-PansyScan_%28cropped%29.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "cucumber-downy-mildew", @@ -1464,7 +1578,7 @@ "Severe yield loss and fruit quality reduction" ], "causes": [ - "Cool to moderate temperatures (60-75\u00b0F) with high humidity and leaf wetness", + "Cool to moderate temperatures (60-75°F) with high humidity and leaf wetness", "Wind-blown spores from distant infected crops", "Overhead irrigation or heavy dew prolonging leaf wetness", "Susceptible varieties lacking genetic resistance" @@ -1477,7 +1591,7 @@ "Harvest remaining fruit quickly and cure indoors" ], "prevention": [ - "Plant resistant varieties \u2014 look for 'DM' resistance (e.g., 'County Crop', 'Salad Bush')", + "Plant resistant varieties — look for 'DM' resistance (e.g., 'County Crop', 'Salad Bush')", "Monitor local downy mildew alerts from extension services", "Apply preventive fungicide at first sign of disease in area", "Water at soil level and early in the day", @@ -1489,7 +1603,10 @@ "squash-downy-mildew", "zucchini-downy-mildew" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/PansyScan_%28cropped%29.jpg/960px-PansyScan_%28cropped%29.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "cucumber-angular-leaf-spot", @@ -1506,7 +1623,7 @@ "Raised corky brown lesions on fruit" ], "causes": [ - "Cool, wet weather (60-75\u00b0F) with frequent rain or overhead irrigation", + "Cool, wet weather (60-75°F) with frequent rain or overhead irrigation", "Contaminated seeds carrying the bacteria", "Rain splash spreading bacteria from soil to leaves", "Infected plant debris in soil" @@ -1528,7 +1645,10 @@ "lookalikeDiseaseIds": [ "cucumber-downy-mildew" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/7/76/%27Cercospora_capsici.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "squash-powdery-mildew", @@ -1545,7 +1665,7 @@ "Heavy infection covering entire leaf surface" ], "causes": [ - "Warm weather (70-90\u00b0F) in mid-to-late summer", + "Warm weather (70-90°F) in mid-to-late summer", "Dense vine canopy reducing air circulation", "Excessive nitrogen fertilization", "Spores spreading from nearby cucurbits" @@ -1567,7 +1687,10 @@ "lookalikeDiseaseIds": [ "zucchini-powdery-mildew" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Golovinomyces_sordidus_on_Broadleaf_Plantain_-_Plantago_major_%2844171864324%29.jpg/960px-Golovinomyces_sordidus_on_Broadleaf_Plantain_-_Plantago_major_%2844171864324%29.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "squash-mosaic-virus", @@ -1575,7 +1698,7 @@ "name": "Squash Mosaic Virus", "scientificName": "Squash mosaic virus (SqMV)", "causalAgentType": "viral", - "description": "Squash mosaic virus causes mottling and distortion of squash leaves and fruit. It is spread primarily by aphids and mechanically through contaminated tools and hands. There is no cure for viral infections \u2014 management focuses on prevention and removing infected plants.", + "description": "Squash mosaic virus causes mottling and distortion of squash leaves and fruit. It is spread primarily by aphids and mechanically through contaminated tools and hands. There is no cure for viral infections — management focuses on prevention and removing infected plants.", "symptoms": [ "Mottled light and dark green patterns on leaves", "Leaf distortion, shrinking, and fern-like appearance", @@ -1590,7 +1713,7 @@ "Volunteer squash plants serving as virus reservoirs" ], "treatment": [ - "Remove and destroy infected plants immediately \u2014 do not compost", + "Remove and destroy infected plants immediately — do not compost", "Control aphid populations with insecticidal soap or neem oil", "Wash hands and sanitize tools when moving between plants", "Remove all volunteer squash and cucumber plants from garden", @@ -1606,7 +1729,10 @@ "lookalikeDiseaseIds": [ "zucchini-mosaic-virus" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Squashes_at_Kew_Gardens_IncrEdibles_2013.jpg/960px-Squashes_at_Kew_Gardens_IncrEdibles_2013.jpg", + "imageQuality": "good", + "prevalence": "rare" }, { "id": "squash-downy-mildew", @@ -1623,7 +1749,7 @@ "Severe yield loss" ], "causes": [ - "Cool to moderate temperatures (60-75\u00b0F) with high humidity", + "Cool to moderate temperatures (60-75°F) with high humidity", "Wind-blown spores from distant infections", "Overhead irrigation or heavy dew", "Susceptible varieties" @@ -1646,7 +1772,10 @@ "cucumber-downy-mildew", "zucchini-downy-mildew" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Sunflower_sky_backdrop.jpg/960px-Sunflower_sky_backdrop.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "bean-common-bleach", @@ -1663,7 +1792,7 @@ "Premature leaf drop and reduced pod yield" ], "causes": [ - "Warm (75-85\u00b0F), wet weather with heavy rain or overhead irrigation", + "Warm (75-85°F), wet weather with heavy rain or overhead irrigation", "Infected seed carrying the bacteria", "Wind-driven rain splashing bacteria from soil to plants", "Infected crop debris in soil" @@ -1685,7 +1814,10 @@ "lookalikeDiseaseIds": [ "bean-halo-blight" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Laboratory_outlines_in_plant_pathology_%28IA_laboratoryoutlin00whet%29.pdf/page1-500px-Laboratory_outlines_in_plant_pathology_%28IA_laboratoryoutlin00whet%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "bean-halo-blight", @@ -1702,7 +1834,7 @@ "Pod lesions reducing marketability" ], "causes": [ - "Cool (60-75\u00b0F), wet weather", + "Cool (60-75°F), wet weather", "Infected seed carrying bacteria", "Rain splash and overhead irrigation spreading bacteria", "Volunteer bean plants serving as reservoir" @@ -1715,7 +1847,7 @@ "Remove volunteer bean plants" ], "prevention": [ - "Use certified disease-free seed \u2014 treat seed with hot water (50\u00b0C for 25 min)", + "Use certified disease-free seed — treat seed with hot water (50°C for 25 min)", "Practice crop rotation", "Avoid planting in cool, wet conditions", "Space plants for air circulation", @@ -1724,7 +1856,10 @@ "lookalikeDiseaseIds": [ "bean-common-bleach" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Mung_beans_%28Vigna_radiata%29.jpg/960px-Mung_beans_%28Vigna_radiata%29.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "bean-white-mold", @@ -1741,7 +1876,7 @@ "Pods becoming soft and rotting" ], "causes": [ - "Cool (60-75\u00b0F), wet weather with high humidity", + "Cool (60-75°F), wet weather with high humidity", "Dense plantings creating shaded, humid microclimates", "Sclerotia in soil from previous infections germinating", "Falling petals providing food for fungal growth at soil surface" @@ -1761,7 +1896,10 @@ "Plow deeply to bury sclerotia below 6 inches" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Sunroot_top.jpg/960px-Sunroot_top.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "strawberry-powdery-mildew", @@ -1778,7 +1916,7 @@ "Reduced fruit size and yield" ], "causes": [ - "Moderate temperatures (60-75\u00b0F) with high humidity", + "Moderate temperatures (60-75°F) with high humidity", "Poor air circulation in dense plantings", "Fungus overwintering on leaves and in flower buds", "Excessive nitrogen promoting succulent growth" @@ -1798,7 +1936,10 @@ "Remove all leaves after harvest (bed renovation)" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Marechal_Foch_Grapes%2C_Nova_Scotia.jpg/960px-Marechal_Foch_Grapes%2C_Nova_Scotia.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "strawberry-gray-mold", @@ -1815,7 +1956,7 @@ "Fruit becoming mummified in dry conditions" ], "causes": [ - "Cool (50-70\u00b0F), wet weather during bloom and fruiting", + "Cool (50-70°F), wet weather during bloom and fruiting", "Overhead irrigation or heavy dew during bloom", "Dense plantings reducing air circulation", "Damaged or bruised fruit providing entry points" @@ -1829,13 +1970,16 @@ ], "prevention": [ "Plant in well-drained soil with good air circulation", - "Use drip irrigation \u2014 avoid wetting flowers and fruit", + "Use drip irrigation — avoid wetting flowers and fruit", "Remove all weeds and debris from between rows", "Apply fungicide at first bloom in high-risk areas", "Harvest frequently and remove any damaged fruit promptly" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/xCkZV5hmL757LmnOpWFE0GJrqRqWlNC1H2z4TqS-v_8/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/MC8wNi9Cb3RyeXRp/cy1HcmF5LU1vbGQt/b24tU3RyYXdiZXJy/eS1QbGFudHMuanBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "strawberry-leaf-scorch", @@ -1872,7 +2016,10 @@ "Maintain proper soil drainage" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/c5oBR4uLCQ_0ivGFHwPTQRPb7BVtyWk7fum-2U0UJSk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/YWNlcy5lZHUvd3At/Y29udGVudC91cGxv/YWRzLzIwMjUvMDgv/cGh5dG9waHRob3Jh/LXN0cmF3YmVycnkt/MS0zMDB4MzAwLmpw/Zw", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "mint-downy-mildew", @@ -1889,7 +2036,7 @@ "Premature defoliation in severe cases" ], "causes": [ - "Cool (50-70\u00b0F), wet weather with high humidity", + "Cool (50-70°F), wet weather with high humidity", "Overhead irrigation or heavy dew", "Dense plantings with poor air circulation", "Pathogen overwintering in soil and debris" @@ -1909,7 +2056,10 @@ "Avoid overhead watering" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f5/3_types_of_lentil.png/960px-3_types_of_lentil.png", + "imageQuality": "good", + "prevalence": "common" }, { "id": "mint-rust", @@ -1926,7 +2076,7 @@ "Dark brown pustules (overwintering stage) in late season" ], "causes": [ - "Moderate temperatures (60-75\u00b0F) with high humidity", + "Moderate temperatures (60-75°F) with high humidity", "Fungus overwintering in living plants or debris", "Poor air circulation in dense plantings", "Overhead irrigation keeping leaves wet" @@ -1946,7 +2096,10 @@ "Rotate mint planting location every few years" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Minze.jpg/960px-Minze.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "mint-powdery-mildew", @@ -1983,7 +2136,10 @@ "Plant resistant varieties" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Monarda_clinopodia_inflorescence.jpg/960px-Monarda_clinopodia_inflorescence.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "lavender-gray-mold", @@ -2013,14 +2169,17 @@ "Improve soil drainage if waterlogging is an issue" ], "prevention": [ - "Plant in well-drained soil \u2014 lavender cannot tolerate wet feet", + "Plant in well-drained soil — lavender cannot tolerate wet feet", "Ensure full sun exposure for drying", "Prune regularly to maintain open growth habit", "Avoid heavy mulching against woody stems", "Space plants 2-3 feet apart for air circulation" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/H2WYULyyPBsX20C-QDv3bL51oD2HbzLp8dxiwFGnrRc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4u/dHJlZWhvdXNlLmNv/L2pha2llLW9iamF3/eS1zemFyYS1wbGVz/bi5qcGc", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "lavender-root-rot", @@ -2038,12 +2197,12 @@ ], "causes": [ "Poorly drained or waterlogged soil", - "Overwatering \u2014 lavender needs very little supplemental water", + "Overwatering — lavender needs very little supplemental water", "Heavy clay soil without amendment", "Planting in low-lying wet areas" ], "treatment": [ - "Remove plant and inspect roots \u2014 if roots are mushy, plant is likely unsalvageable", + "Remove plant and inspect roots — if roots are mushy, plant is likely unsalvageable", "If some healthy roots remain, repot in extremely well-draining gritty mix", "Improve site drainage with raised bed or mound", "Apply phosphite fungicide to remaining healthy plants nearby", @@ -2053,11 +2212,14 @@ "Plant only in well-drained, preferably sandy or gravelly soil", "Amend heavy soil with gravel, sand, or grit before planting", "Plant on a mound or raised bed in heavy soils", - "Water sparingly \u2014 established lavender needs little to no irrigation", + "Water sparingly — established lavender needs little to no irrigation", "Choose planting sites with full sun and good air drainage" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "lavender-powdery-mildew", @@ -2094,7 +2256,10 @@ "Apply preventive sulfur spray in spring at new growth emergence" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Blue_rose-artificially_coloured.jpg/960px-Blue_rose-artificially_coloured.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "lettuce-damping-off", @@ -2126,12 +2291,15 @@ "prevention": [ "Use sterile, pasteurized potting mix for seed starting", "Sterilize seed starting trays and containers", - "Avoid overwatering \u2014 keep mix moist but not wet", - "Use bottom heat (70-75\u00b0F) to promote rapid seedling growth", + "Avoid overwatering — keep mix moist but not wet", + "Use bottom heat (70-75°F) to promote rapid seedling growth", "Thin seedlings promptly to reduce humidity" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/DunhillEarlyMorningPipeMurrays.jpg/960px-DunhillEarlyMorningPipeMurrays.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "lettuce-downy-mildew", @@ -2148,7 +2316,7 @@ "Complete crop loss in severe outbreaks" ], "causes": [ - "Cool (50-70\u00b0F), moist weather with high humidity", + "Cool (50-70°F), moist weather with high humidity", "Wind-blown spores from distant infections", "Overhead irrigation or heavy dew", "Susceptible varieties lacking current resistance genes" @@ -2168,7 +2336,10 @@ "Apply preventive fungicide in high-risk areas" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Lactucaserriola2web.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "lettuce-tip-burn", @@ -2198,14 +2369,17 @@ "Remove outer affected leaves to improve head quality" ], "prevention": [ - "Maintain consistent soil moisture \u2014 never let soil dry out completely", + "Maintain consistent soil moisture — never let soil dry out completely", "Ensure soil pH is 6.0-6.8 for optimal calcium availability", - "Use balanced fertilizer \u2014 avoid excess potassium", + "Use balanced fertilizer — avoid excess potassium", "Choose resistant varieties such as 'Salad Bowl' or 'Black Seeded Simpson'", - "Grow in moderate temperatures \u2014 avoid extreme heat" + "Grow in moderate temperatures — avoid extreme heat" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Medical_Heritage_Library_%28IA_63841040R.nlm.nih.gov%29.pdf/page1-500px-Medical_Heritage_Library_%28IA_63841040R.nlm.nih.gov%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "cabbage-black-rot", @@ -2222,7 +2396,7 @@ "Plant collapse and death in severe cases" ], "causes": [ - "Warm (75-85\u00b0F), wet weather", + "Warm (75-85°F), wet weather", "Infected seed or transplants", "Flea beetle wounds providing bacterial entry points", "Contaminated soil from previous brassica crops" @@ -2232,7 +2406,7 @@ "Apply copper-based bactericide to remaining plants", "Control flea beetles to reduce entry wounds", "Improve air circulation and drainage", - "No cure exists \u2014 focus on protecting remaining healthy tissue" + "No cure exists — focus on protecting remaining healthy tissue" ], "prevention": [ "Use certified disease-free seed and transplants", @@ -2242,7 +2416,10 @@ "Choose resistant varieties such as 'Endurance' or 'Marvel'" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Cabbage_and_cross_section_on_white.jpg/960px-Cabbage_and_cross_section_on_white.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "cabbage-downy-mildew", @@ -2259,7 +2436,7 @@ "Heads becoming loose and unmarketable" ], "causes": [ - "Cool (50-70\u00b0F), wet weather with high humidity", + "Cool (50-70°F), wet weather with high humidity", "Overhead irrigation or heavy dew", "Dense plantings with poor air circulation", "Wind-blown spores from nearby crucifers" @@ -2279,7 +2456,10 @@ "Apply preventive fungicide in high-risk areas" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Chinakohl_3_Falscher_Mehltau-Brand.jpg/960px-Chinakohl_3_Falscher_Mehltau-Brand.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "cabbage-fusarium-yellows", @@ -2297,7 +2477,7 @@ ], "causes": [ "Soil-borne fungal spores from previous infected crops", - "Warm soil temperatures (75-85\u00b0F)", + "Warm soil temperatures (75-85°F)", "Acidic soil (pH below 6.0) favoring fungal activity", "Contaminated soil or transplants" ], @@ -2306,7 +2486,7 @@ "Raise soil pH to 6.5-7.0 with lime to suppress fungus", "Apply Trichoderma-based biological fungicide to soil", "Solarize soil in hot weather", - "No chemical cure \u2014 focus on soil management" + "No chemical cure — focus on soil management" ], "prevention": [ "Practice 3-4 year crop rotation with non-brassica crops", @@ -2316,7 +2496,10 @@ "Avoid planting in soil with history of Fusarium" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Index_of_organisms_and_non-parasitic_diseases_in_Plant_disease_reporter%2C_supplements_XXXI-XXXVII%2C_1924_%28IA_indexoforganisms38vanm%29.pdf/page1-960px-Index_of_organisms_and_non-parasitic_diseases_in_Plant_disease_reporter%2C_supplements_XXXI-XXXVII%2C_1924_%28IA_indexoforganisms38vanm%29.pdf.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "sunflower-downy-mildew", @@ -2347,13 +2530,16 @@ ], "prevention": [ "Use fungicide-treated seed or certified clean seed", - "Plant resistant hybrids \u2014 resistance is widely available", + "Plant resistant hybrids — resistance is widely available", "Practice 2-3 year crop rotation", "Avoid planting in cool, wet soils", "Remove volunteer sunflowers from field" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Plasmopara_halstedii_R.H._07.jpg/960px-Plasmopara_halstedii_R.H._07.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "sunflower-rust", @@ -2370,7 +2556,7 @@ "Reduced seed yield and quality" ], "causes": [ - "Warm (70-85\u00b0F), moist weather", + "Warm (70-85°F), moist weather", "Fungus overwintering on debris and volunteer plants", "Wind-blown spores from distant infections", "Susceptible varieties" @@ -2390,7 +2576,10 @@ "Monitor fields regularly for early detection" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Schorseneer_plant_Scorzonera_hispanica.jpg/960px-Schorseneer_plant_Scorzonera_hispanica.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "sunflower-powdery-mildew", @@ -2407,7 +2596,7 @@ "Stunted growth in severe cases" ], "causes": [ - "Warm weather (70-90\u00b0F) with moderate humidity", + "Warm weather (70-90°F) with moderate humidity", "Dense plantings with poor air circulation", "Spores spreading from nearby infected plants", "Excessive nitrogen promoting succulent growth" @@ -2427,7 +2616,10 @@ "Apply preventive sulfur spray at first sign" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Sunroot_top.jpg/960px-Sunroot_top.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "fiddle-leaf-fig-leaf-spot", @@ -2457,14 +2649,17 @@ "Wipe remaining leaves with neem oil solution" ], "prevention": [ - "Avoid getting water on leaves \u2014 water at soil level only", + "Avoid getting water on leaves — water at soil level only", "If showering plant, ensure leaves dry completely within hours", "Maintain good air circulation", "Wipe leaves monthly with clean damp cloth", "Allow soil to dry 1-2 inches between waterings" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/0w67jSvytmrzD0aW7Pnj66RMkHm0t-1XB9XYMelmIFg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9iMjk0/NTA0MS5zbXVzaGNk/bi5jb20vMjk0NTA0/MS93cC1jb250ZW50/L3VwbG9hZHMvMjAx/OS8wMS9JTUdfOTc2/Mi0xLmpwZz9sb3Nz/eT0xJnN0cmlwPTEm/d2VicD0x", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "fiddle-leaf-fig-root-rot", @@ -2472,7 +2667,7 @@ "name": "Root Rot", "scientificName": "Pythium / Phytophthora spp.", "causalAgentType": "fungal", - "description": "Root rot is the most common cause of fiddle leaf fig decline indoors. It is almost always caused by overwatering in heavy potting soil. The plant's large leaves are a major clue \u2014 they indicate substantial water needs but also significant water storage, making watering frequency tricky.", + "description": "Root rot is the most common cause of fiddle leaf fig decline indoors. It is almost always caused by overwatering in heavy potting soil. The plant's large leaves are a major clue — they indicate substantial water needs but also significant water storage, making watering frequency tricky.", "symptoms": [ "Yellowing leaves, especially lower leaves", "Brown spots on leaves (can be confused with leaf spot)", @@ -2481,7 +2676,7 @@ "Soil remaining wet for days after watering" ], "causes": [ - "Overwatering \u2014 watering before soil has dried sufficiently", + "Overwatering — watering before soil has dried sufficiently", "Pot without drainage holes", "Heavy potting soil without drainage amendments", "Watering on fixed schedule rather than checking moisture" @@ -2501,7 +2696,10 @@ "Reduce watering in winter when growth slows" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Orkide_i_finstua_mot_vest.jpg/960px-Orkide_i_finstua_mot_vest.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "fiddle-leaf-fig-spider-mites", @@ -2538,7 +2736,10 @@ "Avoid excessive nitrogen fertilizer" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/KsAcvKooEPszWNcjEkd60Yh0RwH9mkuAJYDyBk5bAYk/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZG9zc2llcmJsb2cu/Y29tL3dwLWNvbnRl/bnQvdXBsb2Fkcy8y/MDIwLzAyLzFoZWFk/ZXItdHJlYXQtc3Bp/ZGVyLW1pdGVzLWZp/ZGRsZS1pZy01MTJ4/NzY4LmpwZw", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "aloe-root-rot", @@ -2555,7 +2756,7 @@ "Foul odor from soil" ], "causes": [ - "Overwatering \u2014 watering before soil has dried completely", + "Overwatering — watering before soil has dried completely", "Pot without drainage holes", "Regular potting soil instead of cactus mix", "Winter watering when plant is dormant" @@ -2570,12 +2771,15 @@ "prevention": [ "Use cactus/succulent mix with excellent drainage", "Water only when soil is 100% dry", - "Use pot with drainage holes \u2014 terracotta is ideal", + "Use pot with drainage holes — terracotta is ideal", "Reduce watering in winter", "Use soak and dry method" ], "lookalikeDiseaseIds": [], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Orkide_i_finstua_mot_vest.jpg/960px-Orkide_i_finstua_mot_vest.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "aloe-mealybugs", @@ -2612,7 +2816,10 @@ "Avoid over-fertilizing" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/L-rTzPOjDU_1iPtea1Jjj8Lfxfn9LG9UdkOJLG1rmlM/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9jZG4w/LnRoZWRhaWx5ZWNv/LmNvbS9lbi9wb3N0/cy85LzEvNi9sZWFm/X3Nwb3RfZGlzZWFz/ZV82MTlfMF82MDAu/anBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "aloe-sunburn", @@ -2625,7 +2832,7 @@ "Brown to reddish-brown scorched patches on leaf surfaces", "Scarring concentrated on leaves facing the sun", "Leaves becoming crispy and dry in severe cases", - "Permanent brown scarring \u2014 tissue does not recover", + "Permanent brown scarring — tissue does not recover", "Plant turning reddish overall (can be stress response or etting, which is different)" ], "causes": [ @@ -2636,20 +2843,23 @@ ], "treatment": [ "Move to bright indirect light to prevent further damage", - "Do not remove burned leaves \u2014 they still function", + "Do not remove burned leaves — they still function", "Gradually acclimate over 2-3 weeks if outdoor growing is desired", - "Maintain normal watering \u2014 do not overwater stressed plant", + "Maintain normal watering — do not overwater stressed plant", "Trim only completely dead crispy tissue" ], "prevention": [ "Acclimate indoor aloe to outdoor sun gradually over 2-3 weeks", "Provide morning sun with afternoon shade", "Use shade cloth for intense summer sun", - "Monitor leaf color \u2014 green indicates appropriate light", + "Monitor leaf color — green indicates appropriate light", "Start new outdoor plants in partial shade" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/8/87/Hand2ndburn.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "jasmine-powdery-mildew", @@ -2666,7 +2876,7 @@ "Premature leaf drop in severe cases" ], "causes": [ - "Warm days (70-80\u00b0F) with cool, humid nights", + "Warm days (70-80°F) with cool, humid nights", "Poor air circulation in dense growth", "Overhead irrigation", "Excessive nitrogen fertilization" @@ -2686,7 +2896,10 @@ "Apply preventive sulfur spray at first sign" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Wooly_aphidson_jasmine_plant.jpg/960px-Wooly_aphidson_jasmine_plant.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "jasmine-black-spot", @@ -2723,7 +2936,10 @@ "Apply preventive fungicide in spring" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/8/84/Olivesfromjordan.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "jasmine-viral-mosaic", @@ -2746,7 +2962,7 @@ "Infected cutting material used for propagation" ], "treatment": [ - "Remove and destroy infected plants \u2014 do not compost", + "Remove and destroy infected plants — do not compost", "Sanitize all tools with 10% bleach solution", "Wash hands thoroughly, especially after handling tobacco products", "No chemical treatment can cure viral infection", @@ -2760,7 +2976,10 @@ "Quarantine new plants" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/m2lePdBbR7N-OUW_l4saDGEmJ-b6TKpv719fJAzP44A/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9nYXJk/ZW5lcnNwYXRoLmNv/bS93cC1jb250ZW50/L3VwbG9hZHMvMjAy/My8wNy9KYXNtaW5l/LURpc2Vhc2VzLUZl/YXR1cmUuanBn", + "imageQuality": "fallback", + "prevalence": "rare" }, { "id": "chili-blossom-end-rot", @@ -2801,7 +3020,10 @@ "blossom-end-rot", "eggplant-blossom-end-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/G27ys6xN2xOxuIim46kDPLGDqsMGJkQCTYp4Qxnx3AE/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/bWlzc291cmlib3Rh/bmljYWxnYXJkZW4u/b3JnL1BvcnRhbHMv/MC9HYXJkZW5pbmcv/R2FyZGVuaW5nJTIw/SGVscC9pbWFnZXMv/UGVzdHMvQmxvc3Nv/bV9FbmRfUm90X29m/X1RvbWF0bzIwNTku/anBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "chili-phomopsis-bligh", @@ -2818,7 +3040,7 @@ "Branch dieback and plant collapse" ], "causes": [ - "Hot (85-95\u00b0F), humid weather", + "Hot (85-95°F), humid weather", "Overhead irrigation keeping foliage wet", "Dense plantings with poor air circulation", "Infected plant debris in soil" @@ -2838,7 +3060,10 @@ "Choose resistant varieties when available" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/Bw2HKsqR-VUW2dIDs23YmpJOT2aVmbzbQljpQc-m6FY/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/Z2FyZGVuZXJzLmNv/bS9jZG4vc2hvcC9h/cnRpY2xlcy83Mjk2/LVBob21vcHNpcy1C/bGlnaHQtZWdncGxh/bnQuanBnP3Y9MTc1/NDkzODQ1NyZ3aWR0/aD0zMjA", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "chili-cercospora-leaf-spot", @@ -2875,7 +3100,10 @@ "Choose resistant varieties" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f5/3_types_of_lentil.png/960px-3_types_of_lentil.png", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "eggplant-blossom-end-rot", @@ -2916,7 +3144,10 @@ "pepper-blossom-end-rot", "chili-blossom-end-rot" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/M5VmsJWcGGV3jd1JRaELFW-dUZY4QYEpeeBOY6s1ZpI/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93cGNk/bi53ZWIud3N1LmVk/dS9leHRlbnNpb24v/dXBsb2Fkcy9zaXRl/cy8zMS9wZXBwZXIt/Ymxvc3NvbS1lbmQt/cm90LTFMLTM5Nngy/OTAuanBn", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "eggplant-verruculosis", @@ -2953,7 +3184,10 @@ "Choose resistant varieties when available" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/jJoYdSyzMN7ZkgFRr1RDeFMSBitksjk9LTIAsCI1jHg/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly90aHVt/YnMuZHJlYW1zdGlt/ZS5jb20vYi9sZWFm/LWVnZ3BsYW50LWFs/YmluaXNtLXN5bXB0/b20tbGVhZi1lZ2dw/bGFudC1hbGJpbmlz/bS1zeW1wdG9tLTQ1/MzYzOTQ3My5qcGc", + "imageQuality": "fallback", + "prevalence": "uncommon" }, { "id": "eggplant-fusarium-wilt", @@ -2990,7 +3224,10 @@ "Use resistant varieties when available" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/54/Fusarium_wilt_symptom_tobacco.jpg/960px-Fusarium_wilt_symptom_tobacco.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "spinach-downy-mildew", @@ -3007,7 +3244,7 @@ "Reduced leaf quality and yield" ], "causes": [ - "Cool (50-65\u00b0F), moist weather with high humidity", + "Cool (50-65°F), moist weather with high humidity", "Wind-blown spores from distant infections", "Overhead irrigation or heavy dew", "Infected seed carrying spores" @@ -3027,7 +3264,10 @@ "Plant in well-drained soil" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/b/b8/Downy_and_Powdery_mildew_on_grape_leaf.JPG", + "imageQuality": "good", + "prevalence": "common" }, { "id": "spinach-powdery-mildew", @@ -3044,7 +3284,7 @@ "Premature senescence" ], "causes": [ - "Warm weather (70-85\u00b0F) with moderate humidity", + "Warm weather (70-85°F) with moderate humidity", "Poor air circulation", "Excessive nitrogen", "Spores spreading from nearby infections" @@ -3064,7 +3304,10 @@ "Plant in cool seasons when possible" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Carica_papaya_-_K%C3%B6hler%E2%80%93s_Medizinal-Pflanzen-029.jpg/960px-Carica_papaya_-_K%C3%B6hler%E2%80%93s_Medizinal-Pflanzen-029.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "fern-rust", @@ -3101,7 +3344,10 @@ "Maintain appropriate humidity (not excessive)" ], "lookalikeDiseaseIds": [], - "severity": "low" + "severity": "low", + "imageUrl": "https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "fern-root-rot", @@ -3138,7 +3384,10 @@ "Use terracotta pots for better drainage" ], "lookalikeDiseaseIds": [], - "severity": "high" + "severity": "high", + "imageUrl": "https://imgs.search.brave.com/xid_vdIjONng5nROkR-b71lw3slMuqqzRYf65itVr4Q/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly9hZ3Jp/Y3VsdHVyZS52aWMu/Z292LmF1L2Jpb3Nl/Y3VyaXR5L3BsYW50/LWRpc2Vhc2VzL3Zl/Z2V0YWJsZS1kaXNl/YXNlcy9waHl0b3Bo/dGhvcmEtcm9vdC1y/b3Qtb2YtdG9tYXRv/ZXMvcGh5dG9waHRo/b3JhLXJvb3Qtcm90/LXRvbWF0b2VzLTIu/cG5n", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "fern-botrytis", @@ -3171,11 +3420,14 @@ "Maintain good air circulation", "Remove dead fronds promptly", "Avoid overwatering", - "Keep plant at 60-75\u00b0F", + "Keep plant at 60-75°F", "Do not crowd plants together" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/AsparagusPlumosus2.jpg/960px-AsparagusPlumosus2.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "daisy-powdery-mildew", @@ -3212,7 +3464,10 @@ "Apply preventive sulfur spray at first sign" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/AwTYk5ex0GDr38LBt-uk9Pi7yOic_sTSeiTVYlEtNW4/rs:fit:0:180:1:0/g:ce/aHR0cHM6Ly93d3cu/dW1hc3MuZWR1L2Fn/cmljdWx0dXJlLWZv/b2QtZW52aXJvbm1l/bnQvc2l0ZXMvZGVm/YXVsdC9maWxlcy9z/dHlsZXMvMTUweDE1/MC9wdWJsaWMvZmFj/dC1zaGVldHMvaW1h/Z2VzL3Bvd2Rlcnlf/bWlsZF8wMy5qcGc_/aXRvaz1kZTZVMFZ0/UA", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "daisy-rust", @@ -3249,7 +3504,10 @@ "Apply preventive fungicide in spring" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/jv3P88EcgRSZTzKuvxRUJ2E07WKA3guVEruoY4l5Hfc/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/dGhlZ2FyZGVud2Vi/c2l0ZS5jb20vdXBs/b2Fkcy84LzQvNS81/Lzg0NTU3MjQyL3J1/c3QtZm94Z2xvdmUt/YXVnLTJfb3JpZy5q/cGc", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "daisy-downy-mildew", @@ -3286,7 +3544,10 @@ "Apply preventive fungicide in cool wet weather" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://imgs.search.brave.com/h65q4ea2_EVIu5_NsJVPwUOVdrdVfhZGcr42TPFFEF0/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjQvMDkvZG93/bnktbWlsZGV3LXZl/Z2V0YWJsZS1nYXJk/ZW4uanBn", + "imageQuality": "fallback", + "prevalence": "common" }, { "id": "zucchini-powdery-mildew", @@ -3325,7 +3586,10 @@ "lookalikeDiseaseIds": [ "squash-powdery-mildew" ], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Cucumis_metuliferus_fruit_-_whole_and_cross_section.jpg/960px-Cucumis_metuliferus_fruit_-_whole_and_cross_section.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "zucchini-mosaic-virus", @@ -3336,7 +3600,7 @@ "description": "Cucumber mosaic virus causes severe mottling, distortion, and stunting of zucchini plants. It is spread by aphids and mechanically through contaminated tools. Infected fruit becomes misshapen, bitter, and unmarketable. There is no cure.", "symptoms": [ "Severe mottling of light and dark green on leaves", - "Leaf distortion \u2014 narrow, strap-like, or fern-like appearance", + "Leaf distortion — narrow, strap-like, or fern-like appearance", "Severe stunting of entire plant", "Fruit becoming misshapen, bumpy, and bitter", "Plant death in severe cases" @@ -3348,7 +3612,7 @@ "Volunteer cucurbits serving as virus reservoir" ], "treatment": [ - "Remove and destroy infected plants \u2014 do not compost", + "Remove and destroy infected plants — do not compost", "Control aphids with insecticidal soap or neem oil", "Sanitize tools and wash hands", "Remove volunteer cucurbits", @@ -3364,7 +3628,10 @@ "lookalikeDiseaseIds": [ "squash-mosaic-virus" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Squashes_at_Kew_Gardens_IncrEdibles_2013.jpg/960px-Squashes_at_Kew_Gardens_IncrEdibles_2013.jpg", + "imageQuality": "good", + "prevalence": "rare" }, { "id": "zucchini-downy-mildew", @@ -3404,7 +3671,10 @@ "cucumber-downy-mildew", "squash-downy-mildew" ], - "severity": "high" + "severity": "high", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Cabbage_and_cross_section_on_white.jpg/960px-Cabbage_and_cross_section_on_white.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "cactus-root-rot", @@ -3436,14 +3706,17 @@ "prevention": [ "Use extremely well-draining cactus mix (70% mineral, 30% organic)", "Water only when soil is 100% dry", - "Use pot with drainage holes \u2014 terracotta is ideal", + "Use pot with drainage holes — terracotta is ideal", "Eliminate watering in winter dormancy", "Use soak and dry method" ], "lookalikeDiseaseIds": [ "cactus-stem-rot" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Peperomia_trifolia_2011-01-17.jpg/960px-Peperomia_trifolia_2011-01-17.jpg", + "imageQuality": "good", + "prevalence": "common" }, { "id": "cactus-mealybugs", @@ -3480,7 +3753,10 @@ "Avoid over-fertilizing" ], "lookalikeDiseaseIds": [], - "severity": "moderate" + "severity": "moderate", + "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Burmese_python_%286887388927%29.jpg/960px-Burmese_python_%286887388927%29.jpg", + "imageQuality": "good", + "prevalence": "uncommon" }, { "id": "cactus-stem-rot", @@ -3503,7 +3779,7 @@ "Contaminated tools or soil" ], "treatment": [ - "Cut away all infected tissue with sterile knife \u2014 cut well beyond visible rot", + "Cut away all infected tissue with sterile knife — cut well beyond visible rot", "Let cut surface callus for 1-2 weeks", "Apply cinnamon powder to cut surfaces as antifungal", "Repot in fresh sterile cactus mix if stem is salvageable", @@ -3519,6 +3795,9 @@ "lookalikeDiseaseIds": [ "cactus-root-rot" ], - "severity": "critical" + "severity": "critical", + "imageUrl": "https://imgs.search.brave.com/PF-Eqq7LSywJp8gzOgPppbHMfsXG4Ruj9zLZKkmxYRU/rs:fit:500:0:1:0/g:ce/aHR0cHM6Ly93d3cu/ZXBpY2dhcmRlbmlu/Zy5jb20vd3AtY29u/dGVudC91cGxvYWRz/LzIwMjMvMTIvRnVu/Z3VzLWRpc2Vhc2Uu/anBn", + "imageQuality": "fallback", + "prevalence": "uncommon" } -] \ No newline at end of file +] diff --git a/apps/web/src/lib/api/browse.ts b/apps/web/src/lib/api/browse.ts index 85317cf..ab644b4 100644 --- a/apps/web/src/lib/api/browse.ts +++ b/apps/web/src/lib/api/browse.ts @@ -5,7 +5,7 @@ import { sql, eq } from "drizzle-orm"; import { getDb } from "@/lib/db/index"; -import { plants, diseases } from "@/lib/db/schema"; +import { plants, diseases, plantViews } from "@/lib/db/schema"; import type { PlantCardData } from "@/components/PlantCard"; export type { PlantCardData }; @@ -24,10 +24,14 @@ export async function getBrowsePlants(): Promise { scientificName: plants.scientificName, family: plants.family, category: plants.category, + imageUrl: plants.imageUrl, + updatedAt: plants.updatedAt, + viewCount: sql`COALESCE(${plantViews.viewCount}, 0)`, diseaseCount: sql`COUNT(${diseases.id})`, }) .from(plants) .leftJoin(diseases, eq(diseases.plantId, plants.id)) + .leftJoin(plantViews, eq(plantViews.plantId, plants.id)) .groupBy(plants.id) .orderBy(plants.commonName); @@ -37,6 +41,9 @@ export async function getBrowsePlants(): Promise { scientificName: r.scientificName, family: r.family, category: r.category, + imageUrl: r.imageUrl, + updatedAt: r.updatedAt, + viewCount: r.viewCount, diseaseCount: r.diseaseCount, })); } @@ -53,6 +60,7 @@ export async function getBrowsePlant(id: string): Promise scientificName: plants.scientificName, family: plants.family, category: plants.category, + imageUrl: plants.imageUrl, diseaseCount: sql`COUNT(${diseases.id})`, }) .from(plants) diff --git a/apps/web/src/lib/api/diseases-db.ts b/apps/web/src/lib/api/diseases-db.ts index e6c2472..c22fc0b 100644 --- a/apps/web/src/lib/api/diseases-db.ts +++ b/apps/web/src/lib/api/diseases-db.ts @@ -18,6 +18,7 @@ import type { Plant, PlantListParams, PlantWithDiseases, + Prevalence, Severity, PlantCategory, } from "@/lib/types"; @@ -50,6 +51,7 @@ function toDisease(row: typeof diseases.$inferSelect): Disease { prevention: row.prevention as string[], lookalikeDiseaseIds: (row.lookalikeIds as string[]) ?? [], severity: row.severity as Severity, + prevalence: (row.prevalence as Prevalence) ?? "uncommon", imageUrl: (row.imageUrl as string) || undefined, }; } @@ -278,6 +280,7 @@ export async function validateKnowledgeBase(): Promise { "environmental", ]; const validSeverities: Severity[] = ["low", "moderate", "high", "critical"]; + const validPrevalences: Prevalence[] = ["common", "uncommon", "rare"]; const db = getDb(); @@ -328,6 +331,11 @@ export async function validateKnowledgeBase(): Promise { errors.push(`Disease "${d.id}" has invalid severity: ${full.severity}`); } + // Valid prevalence + if (full.prevalence && !validPrevalences.includes(full.prevalence as Prevalence)) { + errors.push(`Disease "${d.id}" has invalid prevalence: ${full.prevalence}`); + } + // Minimum counts const symptoms = full.symptoms as string[]; const causes = full.causes as string[]; diff --git a/apps/web/src/lib/db.ts b/apps/web/src/lib/db.ts index 35502f0..8a6388c 100644 --- a/apps/web/src/lib/db.ts +++ b/apps/web/src/lib/db.ts @@ -10,7 +10,7 @@ */ import { createClient, type InValue } from "@libsql/client"; -import type { Plant, Disease, CausalAgentType, Severity } from "./types"; +import type { Plant, Disease, CausalAgentType, Prevalence, Severity } from "./types"; // ─── Client ────────────────────────────────────────────────────────────────── @@ -173,6 +173,7 @@ export function rowToDisease(row: Record): Disease { prevention: JSON.parse(row.prevention as string) as string[], lookalikeDiseaseIds: JSON.parse(row.lookalike_ids as string) as string[], severity: row.severity as Severity, + prevalence: (row.prevalence as Prevalence) ?? "uncommon", }; } diff --git a/apps/web/src/lib/db/schema.ts b/apps/web/src/lib/db/schema.ts index f1187ca..2216eda 100644 --- a/apps/web/src/lib/db/schema.ts +++ b/apps/web/src/lib/db/schema.ts @@ -54,6 +54,11 @@ export const diseases = sqliteTable( treatment: text("treatment", { mode: "json" }).notNull().default([]).$type(), prevention: text("prevention", { mode: "json" }).notNull().default([]).$type(), lookalikeIds: text("lookalike_ids", { mode: "json" }).notNull().default([]).$type(), + prevalence: text("prevalence", { + enum: ["common", "uncommon", "rare"], + }) + .notNull() + .default("uncommon"), severity: text("severity", { enum: ["low", "moderate", "high", "critical"], }).notNull(), @@ -70,6 +75,7 @@ export const diseases = sqliteTable( plantIdIdx: index("idx_diseases_plant_id").on(table.plantId), causalAgentIdx: index("idx_diseases_causal_agent").on(table.causalAgentType), severityIdx: index("idx_diseases_severity").on(table.severity), + prevalenceIdx: index("idx_diseases_prevalence").on(table.prevalence), }), ); @@ -92,6 +98,21 @@ export const scrapeSources = sqliteTable("scrape_sources", { .default(sql`(datetime('now'))`), }); +// ─── Plant Views Table ─────────────────────────────────────────────────────── + +export const plantViews = sqliteTable( + "plant_views", + { + plantId: text("plant_id") + .primaryKey() + .references(() => plants.id), + viewCount: integer("view_count").notNull().default(0), + }, + (table) => ({ + viewCountIdx: index("idx_plant_views_count").on(table.viewCount), + }), +); + // ─── Relation Inference ────────────────────────────────────────────────────── export const plantsRelations = {}; diff --git a/apps/web/src/lib/image-processing.ts b/apps/web/src/lib/image-processing.ts index 8b36698..fcab83f 100644 --- a/apps/web/src/lib/image-processing.ts +++ b/apps/web/src/lib/image-processing.ts @@ -1,21 +1,21 @@ /** * Client-side image preprocessing pipeline. * - * Resizes images to model-expected dimensions (224×224 by default), + * Resizes images to model-expected dimensions (160×160 by default), * converts RGBA → RGB, normalizes pixel values, and produces flat * Float32Array tensors ready for ML inference or base64 transmission. * - * Tensor shape: [1, 3, 224, 224] — NCHW layout matching MobileNet / ResNet. + * Tensor shape: [1, 3, 160, 160] — NCHW layout matching MobileNetV2. * * Configurable via env: - * IMAGE_MODEL_SIZE — target dimension (default 224) + * IMAGE_MODEL_SIZE — target dimension (default 160) * IMAGE_MEAN_R/G/B — per-channel mean for normalization (default 0.485, 0.456, 0.406 — ImageNet) * IMAGE_STD_R/G/B — per-channel std for normalization (default 0.229, 0.224, 0.225 — ImageNet) */ // ─── Configuration ─────────────────────────────────────────────────────────── -const DEFAULT_MODEL_SIZE = 224; +const DEFAULT_MODEL_SIZE = 160; const DEFAULT_MEAN = [0.485, 0.456, 0.406] as const; // ImageNet RGB means const DEFAULT_STD = [0.229, 0.224, 0.225] as const; // ImageNet RGB stds diff --git a/apps/web/src/lib/ml/inference.test.ts b/apps/web/src/lib/ml/inference.test.ts index 8338060..88083a2 100644 --- a/apps/web/src/lib/ml/inference.test.ts +++ b/apps/web/src/lib/ml/inference.test.ts @@ -130,12 +130,12 @@ describe("createRandomTensor", () => { }); describe("INPUT_SHAPE and INPUT_SIZE", () => { - it("INPUT_SHAPE is [1, 3, 224, 224]", () => { - expect(INPUT_SHAPE).toEqual([1, 3, 224, 224]); + it("INPUT_SHAPE is [1, 3, 160, 160]", () => { + expect(INPUT_SHAPE).toEqual([1, 3, 160, 160]); }); - it("INPUT_SIZE equals 3 * 224 * 224", () => { - expect(INPUT_SIZE).toBe(3 * 224 * 224); + it("INPUT_SIZE equals 3 * 160 * 160", () => { + expect(INPUT_SIZE).toBe(3 * 160 * 160); }); it("DEFAULT_TOP_K is 5", () => { diff --git a/apps/web/src/lib/ml/inference.ts b/apps/web/src/lib/ml/inference.ts index 5362616..a6fbbbc 100644 --- a/apps/web/src/lib/ml/inference.ts +++ b/apps/web/src/lib/ml/inference.ts @@ -15,18 +15,18 @@ import { softmaxFloat32, getTopKFloat32 } from "./confidence"; /** Number of top predictions to return */ export const DEFAULT_TOP_K = 5; -/** Input tensor shape: [batch=1, channels=3, height=224, width=224] */ -export const INPUT_SHAPE: [number, number, number, number] = [1, 3, 224, 224]; +/** Input tensor shape: [batch=1, channels=3, height=160, width=160] */ +export const INPUT_SHAPE: [number, number, number, number] = [1, 3, 160, 160]; /** Expected input tensor length */ -export const INPUT_SIZE = INPUT_SHAPE[1] * INPUT_SHAPE[2] * INPUT_SHAPE[3]; // 3 * 224 * 224 = 150528 +export const INPUT_SIZE = INPUT_SHAPE[1] * INPUT_SHAPE[2] * INPUT_SHAPE[3]; // 3 * 160 * 160 = 76800 // ─── Main Inference ────────────────────────────────────────────────────────── /** * Run the full inference pipeline on a preprocessed image tensor. * - * @param imageTensor - Normalized Float32Array of shape [1, 3, 224, 224] (NCHW) + * @param imageTensor - Normalized Float32Array of shape [1, 3, 160, 160] (NCHW) * @param topK - Number of top predictions to return (default 5) * @returns InferenceResult with top-K predictions and timing */ diff --git a/apps/web/src/lib/ml/model-loader.ts b/apps/web/src/lib/ml/model-loader.ts index 58fb80f..ba90e65 100644 --- a/apps/web/src/lib/ml/model-loader.ts +++ b/apps/web/src/lib/ml/model-loader.ts @@ -196,8 +196,8 @@ async function tryLoadTFJS(): Promise { async predict(tensor: Float32Array): Promise { const startTime = performance.now(); - // Reshape to [1, 3, 224, 224] NCHW → [1, 224, 224, 3] NHWC for TF.js - const inputTensor = tf.tensor4d(Array.from(tensor), [3, 224, 224]) + // Reshape to [1, 3, 160, 160] NCHW → [1, 160, 160, 3] NHWC for TF.js + const inputTensor = tf.tensor4d(Array.from(tensor), [3, 160, 160]) .transpose([1, 2, 0]) .expandDims(0); @@ -220,7 +220,7 @@ async function tryLoadTFJS(): Promise { loaded: true, backend: "tfjs", modelId: MODEL_ID, - numClasses: 95, // Will be updated after model loads + numClasses: 38, // Original PlantVillage model }; }, }; @@ -256,8 +256,8 @@ async function tryLoadONNX(): Promise { async predict(tensor: Float32Array): Promise { const startTime = performance.now(); - // ONNX expects NCHW format: [1, 3, 224, 224] - const inputTensor = new ort.Tensor("float32", tensor, [1, 3, 224, 224]); + // ONNX expects NCHW format: [1, 3, 160, 160] + const inputTensor = new ort.Tensor("float32", tensor, [1, 3, 160, 160]); const feeds = { [session.inputNames[0]]: inputTensor }; const results = await session.run(feeds); @@ -278,7 +278,7 @@ async function tryLoadONNX(): Promise { loaded: true, backend: "onnx", modelId: MODEL_ID, - numClasses: 95, + numClasses: 38, }; }, }; @@ -313,7 +313,7 @@ function createMockModel(): PlantDiseaseModel { loaded: false, backend: "mock", modelId: MODEL_ID, - numClasses: 95, + numClasses: 38, error: "Model files not found. Running in demo mode with mock predictions.", }; }, @@ -326,7 +326,7 @@ function createMockModel(): PlantDiseaseModel { * reproducible but varied predictions. */ function generateMockLogits(tensor: Float32Array): Float32Array { - const numClasses = 95; + const numClasses = 38; const logits = new Float32Array(numClasses); // Simple hash of input for deterministic output diff --git a/apps/web/src/lib/types.ts b/apps/web/src/lib/types.ts index d128277..9a37a2f 100644 --- a/apps/web/src/lib/types.ts +++ b/apps/web/src/lib/types.ts @@ -9,6 +9,9 @@ export type CausalAgentType = "fungal" | "bacterial" | "viral" | "environmental" /** Severity levels for plant diseases */ export type Severity = "low" | "moderate" | "high" | "critical"; +/** How common/prevalent a disease is in the field */ +export type Prevalence = "common" | "uncommon" | "rare"; + /** Plant category for grouping and filtering */ export type PlantCategory = | "vegetable" @@ -69,6 +72,8 @@ export interface Disease { lookalikeDiseaseIds: string[]; /** Overall severity of the disease */ severity: Severity; + /** How common/prevalent this disease is */ + prevalence: Prevalence; /** URL to a representative image showing disease symptoms */ imageUrl?: string; } diff --git a/apps/web/tasks/production-ml-pipeline/01-plantvillage-class-inventory.md b/apps/web/tasks/production-ml-pipeline/01-plantvillage-class-inventory.md new file mode 100644 index 0000000..10cf6f6 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/01-plantvillage-class-inventory.md @@ -0,0 +1,152 @@ +# 01. PlantVillage Class Inventory and Knowledge Base Mapping + +meta: +id: production-ml-pipeline-01 +feature: production-ml-pipeline +priority: P0 +depends_on: [] +tags: [data, mapping, research] + +objective: + +- Document all 38 PlantVillage model output classes +- Map each class index to a definitive disease ID in the knowledge base +- Identify which plants and diseases are missing from the KB and must be added +- Produce a complete, authoritative mapping file that subsequent tasks consume + +deliverables: + +- `src/lib/ml/plantvillage-classes.ts` — definitive mapping of all 38 class indices to structured metadata +- Updated `tasks/production-ml-pipeline/class-mapping-reference.md` — human-readable reference document + +steps: + +1. Document the canonical 38 PlantVillage class labels in order (index 0–37): + + ``` + 0: Apple___Apple_scab + 1: Apple___Black_rot + 2: Apple___Cedar_apple_rust + 3: Apple___healthy + 4: Blueberry___healthy + 5: Cherry_(including_sour)___Powdery_mildew + 6: Cherry_(including_sour)___healthy + 7: Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot + 8: Corn_(maize)___Common_rust_ + 9: Corn_(maize)___Northern_Leaf_Blight + 10: Corn_(maize)___healthy + 11: Grape___Black_rot + 12: Grape___Esca_(Black_Measles) + 13: Grape___Leaf_blight_(Isariopsis_Leaf_Spot) + 14: Grape___healthy + 15: Orange___Haunglongbing_(Citrus_greening) + 16: Peach___Bacterial_spot + 17: Peach___healthy + 18: Pepper,_bell___Bacterial_spot + 19: Pepper,_bell___healthy + 20: Potato___Early_blight + 21: Potato___Late_blight + 22: Potato___healthy + 23: Raspberry___healthy + 24: Soybean___healthy + 25: Squash___Powdery_mildew + 26: Strawberry___Leaf_scorch + 27: Strawberry___healthy + 28: Tomato___Bacterial_spot + 29: Tomato___Early_blight + 30: Tomato___Late_blight + 31: Tomato___Leaf_Mold + 32: Tomato___Septoria_leaf_spot + 33: Tomato___Spider_mites Two-spotted_spider_mite + 34: Tomato___Target_Spot + 35: Tomato___Tomato_Yellow_Leaf_Curl_Virus + 36: Tomato___Tomato_mosaic_virus + 37: Tomato___healthy + ``` + +2. For each class, determine the mapping target: + - **Healthy classes** (13 total: indices 3, 4, 6, 10, 14, 17, 19, 22, 23, 24, 27, 37): map to a special `"healthy"` sentinel. These indicate the model detected no disease. + - **Disease classes with exact KB match**: map directly to existing disease ID. + - 28 → `bacterial-leaf-spot-tomato` (Tomato Bacterial_spot ≈ bacterial-leaf-spot-tomato) + - 29 → `early-blight` + - 30 → `late-blight` + - 32 → `septoria-leaf-spot` + - 25 → `squash-powdery-mildew` + - 26 → `strawberry-leaf-scorch` + - 18 → `pepper-bacterial-wilt` (closest match to Pepper Bacterial_spot) + - **Disease classes needing new KB entries** (no existing disease in our KB): + - 0: Apple_scab → new disease `apple-scab` under plant `apple` + - 1: Apple_black_rot → new disease `apple-black-rot` under plant `apple` + - 2: Apple_cedar_apple_rust → new disease `apple-cedar-apple-rust` under plant `apple` + - 5: Cherry_powdery_mildew → new disease `cherry-powdery-mildew` under plant `cherry` + - 7: Corn_cercospora_leaf_spot → new disease `corn-gray-leaf-spot` under plant `corn` + - 8: Corn_common_rust → new disease `corn-common-rust` under plant `corn` + - 9: Corn_northern_leaf_blight → new disease `corn-northern-leaf-blight` under plant `corn` + - 11: Grape_black_rot → new disease `grape-black-rot` under plant `grape` + - 12: Grape_esca → new disease `grape-esca` under plant `grape` + - 13: Grape_leaf_blight → new disease `grape-leaf-blight` under plant `grape` + - 15: Orange_huanglongbing → new disease `orange-citrus-greening` under plant `orange` + - 16: Peach_bacterial_spot → new disease `peach-bacterial-spot` under plant `peach` + - 20: Potato_early_blight → new disease `potato-early-blight` under plant `potato` + - 21: Potato_late_blight → new disease `potato-late-blight` under plant `potato` + - 31: Tomato_leaf_mold → new disease `tomato-leaf-mold` under plant `tomato` + - 33: Tomato_spider_mites → new disease `tomato-spider-mites` under plant `tomato` + - 34: Tomato_target_spot → new disease `tomato-target-spot` under plant `tomato` + - 35: Tomato_yellow_leaf_curl_virus → new disease `tomato-yellow-leaf-curl-virus` under plant `tomato` + - 36: Tomato_mosaic_virus → new disease `tomato-mosaic-virus` under plant `tomato` + +3. Create the mapping type and data structure in `src/lib/ml/plantvillage-classes.ts`: + + ```typescript + export interface PlantVillageClass { + index: number; + rawLabel: string; + plantId: string; // KB plant slug + diseaseId: string | null; // null for healthy classes + isHealthy: boolean; + displayName: string; // human-readable disease name + } + + export const PLANTVILLAGE_CLASSES: readonly PlantVillageClass[] = [ ... ]; + ``` + +4. For each class, also record: + - The PlantVillage plant name (e.g., "Tomato", "Apple") + - The target KB plantId (e.g., "tomato", "apple") + - The target KB diseaseId (e.g., "early-blight") or null for healthy + - Whether the disease needs to be added to the KB (boolean flag for task 02) + +5. Verify the mapping covers all 38 indices with no gaps or duplicates. + +tests: + +- Unit: mapping has exactly 38 entries +- Unit: indices 0–37 are all present, no gaps +- Unit: each non-healthy entry has a non-null diseaseId +- Unit: each healthy entry has null diseaseId and isHealthy=true +- Unit: no duplicate diseaseIds across non-healthy entries +- Unit: all plantIds are valid slugs (lowercase, kebab-case) + +acceptance_criteria: + +- `src/lib/ml/plantvillage-classes.ts` exports `PLANTVILLAGE_CLASSES` array with exactly 38 entries +- Every index 0–37 maps to exactly one entry +- 13 entries are healthy (isHealthy=true, diseaseId=null) +- 25 entries are diseases with valid plantId and diseaseId +- Each entry includes rawLabel, plantId, diseaseId, displayName +- All new disease IDs follow kebab-case convention matching existing KB pattern +- Reference document `class-mapping-reference.md` lists all 38 classes with their KB mappings + +validation: + +- `npx vitest run src/lib/ml/plantvillage-classes.test.ts` — all mapping tests pass +- Manual review: each of the 25 disease entries maps to a plausible disease in our KB + +notes: + +- This task produces the authoritative mapping consumed by task 02 (KB expansion) and task 03 (label mapping) +- The PlantVillage class order is fixed by the model's training — do NOT reorder +- "Tomato Bacterial_spot" maps to our existing `bacterial-leaf-spot-tomato` — this is the closest match, not a perfect one +- "Pepper Bacterial_spot" maps to `pepper-bacterial-wilt` — imperfect but closest available match +- 10 new plants must be added to the KB: apple, blueberry, cherry, corn, grape, orange, peach, potato, raspberry, soybean +- Blueberry, Raspberry, Soybean only have "healthy" class — still need plant entries for context but no new disease entries diff --git a/apps/web/tasks/production-ml-pipeline/02-label-mapping-implementation.md b/apps/web/tasks/production-ml-pipeline/02-label-mapping-implementation.md new file mode 100644 index 0000000..cda8874 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/02-label-mapping-implementation.md @@ -0,0 +1,149 @@ +# 02. Label Mapping Layer Implementation + +meta: +id: production-ml-pipeline-02 +feature: production-ml-pipeline +priority: P0 +depends_on: [production-ml-pipeline-01] +tags: [implementation, knowledge-base, tests-required] + +objective: + +- Expand the knowledge base to cover all PlantVillage plants and diseases +- Rewrite `src/lib/ml/labels.ts` to use the PlantVillage class mapping from task 01 +- Ensure every model output index resolves to a valid KB disease or the "healthy" sentinel +- The label layer must be the single source of truth for model-index → disease mapping + +deliverables: + +- Updated `src/data/plants.json` — 10 new PlantVillage plants added (apple, blueberry, cherry, corn, grape, orange, peach, potato, raspberry, soybean) +- Updated `src/data/diseases.json` — 19 new disease entries added for PlantVillage diseases not yet in KB +- `src/lib/ml/labels.ts` — fully rewritten to use PlantVillage class mapping +- `src/lib/ml/labels.test.ts` — updated to validate against new mapping +- `scripts/seed-plantvillage-kb.ts` — DB migration script to insert new plants and diseases into Turso + +steps: + +1. **Add 10 new plants to `src/data/plants.json`** — each with proper metadata: + + ```typescript + // New plants needed (PlantVillage coverage): + { id: "apple", commonName: "Apple", scientificName: "Malus domestica", family: "Rosaceae", category: "fruit" } + { id: "cherry", commonName: "Cherry", scientificName: "Prunus avium", family: "Rosaceae", category: "fruit" } + { id: "corn", commonName: "Corn (Maize)", scientificName: "Zea mays", family: "Poaceae", category: "vegetable" } + { id: "grape", commonName: "Grape", scientificName: "Vitis vinifera", family: "Vitaceae", category: "fruit" } + { id: "orange", commonName: "Orange", scientificName: "Citrus sinensis", family: "Rutaceae", category: "fruit" } + { id: "peach", commonName: "Peach", scientificName: "Prunus persica", family: "Rosaceae", category: "fruit" } + { id: "potato", commonName: "Potato", scientificName: "Solanum tuberosum", family: "Solanaceae", category: "vegetable" } + { id: "blueberry", commonName: "Blueberry", scientificName: "Vaccinium corymbosum", family: "Ericaceae", category: "fruit" } + { id: "raspberry", commonName: "Raspberry", scientificName: "Rubus idaeus", family: "Rosaceae", category: "fruit" } + { id: "soybean", commonName: "Soybean", scientificName: "Glycine max", family: "Fabaceae", category: "vegetable" } + ``` + + - Add `imageUrl` for each (use Wikipedia pageimages, same pattern as `fill-plant-images.ts`) + - Add `careSummary` for each + +2. **Add 19 new diseases to `src/data/diseases.json`** — each with full structured data: + - Use the template-based approach from `scripts/disease-templates.ts` where possible + - Source disease details from: + - UW-Madison PDDC factsheets (pddc.wisc.edu) + - Cornell Plant Clinic (plantclinic.cornell.edu) + - University extension publications + - Each disease must have: `id`, `plantId`, `name`, `scientificName`, `causalAgentType`, `description`, `symptoms` (≥3), `causes` (≥2), `treatment` (≥3), `prevention` (≥2), `lookalikeDiseaseIds`, `severity`, `prevalence` + - New disease entries needed: + - apple-scab, apple-black-rot, apple-cedar-apple-rust (plant: apple) + - cherry-powdery-mildew (plant: cherry) + - corn-gray-leaf-spot, corn-common-rust, corn-northern-leaf-blight (plant: corn) + - grape-black-rot, grape-esca, grape-leaf-blight (plant: grape) + - orange-citrus-greening (plant: orange) + - peach-bacterial-spot (plant: peach) + - potato-early-blight, potato-late-blight (plant: potato) + - tomato-leaf-mold, tomato-spider-mites, tomato-target-spot, tomato-yellow-leaf-curl-virus, tomato-mosaic-virus (plant: tomato) + - Use programmatic approach: write a generator script that pulls from UW-Madison PDDC / Cornell factsheets and Wikipedia, following the same pattern as `scripts/generate-full-kb.ts` + +3. **Update lookalikeDiseaseIds** — cross-reference within new diseases: + - Apple scab ↔ Apple black rot (both cause leaf spots on apple) + - Potato early blight ↔ Potato late blight (both affect potato foliage) + - Grape black rot ↔ Grape esca (both cause fruit rot) + - Tomato early blight ↔ Tomato septoria leaf spot ↔ Tomato target spot (all cause leaf lesions) + - Tomato leaf mold ↔ Tomato septoria leaf spot (both cause leaf spots in humid conditions) + +4. **Rewrite `src/lib/ml/labels.ts`** to use the PlantVillage mapping: + + ```typescript + import { PLANTVILLAGE_CLASSES } from "./plantvillage-classes"; + + // Total output classes from model + export const NUM_CLASSES = 38; + + // Index 0–37 → disease lookup + export function getDiseaseIdForIndex(index: number): string { + const entry = PLANTVILLAGE_CLASSES[index]; + if (!entry || entry.isHealthy) return "healthy"; + return entry.diseaseId; + } + + export function getPlantIdForIndex(index: number): string { + return PLANTVILLAGE_CLASSES[index]?.plantId ?? "unknown"; + } + + export function isHealthyClass(index: number): boolean { + return PLANTVILLAGE_CLASSES[index]?.isHealthy ?? false; + } + + // Disease ID → index (for reverse lookup) + export function getIndexForDiseaseId(diseaseId: string): number { + const entry = PLANTVILLAGE_CLASSES.find((c) => c.diseaseId === diseaseId.toLowerCase()); + return entry?.index ?? -1; + } + ``` + +5. **Remove old assumptions** — the old labels.ts assumed 95 classes (93 diseases + healthy + unknown). Delete all references to `diseases.json` index ordering from labels.ts. The mapping is now defined by `plantvillage-classes.ts`, not by JSON file order. + +6. **Create DB migration script** `scripts/seed-plantvillage-kb.ts`: + - Read updated `src/data/plants.json` and `src/data/diseases.json` + - Insert new plants and diseases into Turso DB using Drizzle ORM + - Use UPSERT (INSERT OR REPLACE) to be idempotent + - Log what was inserted/updated + +7. **Run the migration** to populate the DB with new data. + +tests: + +- Unit: `labels.test.ts` validates all 38 indices map correctly +- Unit: `getDiseaseIdForIndex(29)` returns `"early-blight"` +- Unit: `getDiseaseIdForIndex(3)` returns `"healthy"` (Apple healthy class) +- Unit: `getIndexForDiseaseId("early-blight")` returns `29` +- Unit: `isHealthyClass(37)` returns `true` (Tomato healthy) +- Unit: `isHealthyClass(29)` returns `false` (Tomato Early_blight) +- Unit: `getPlantIdForIndex(0)` returns `"apple"` +- Unit: All 25 non-healthy diseaseIds resolve to real DB entries via `getDiseaseById()` +- Integration: `scripts/seed-plantvillage-kb.ts` runs without errors, inserts all 10 plants and 19 diseases +- Integration: After seeding, DB query for each new disease returns a complete record + +acceptance_criteria: + +- `PLANTVILLAGE_CLASSES` in labels.ts has exactly 38 entries matching model output order +- 13 healthy indices correctly return "healthy" from `getDiseaseIdForIndex()` +- 25 disease indices correctly return valid diseaseIds +- All 10 new plants exist in `src/data/plants.json` with valid metadata and imageUrl +- All 19 new diseases exist in `src/data/diseases.json` with full structured data (symptoms, treatment, prevention, etc.) +- DB migration script runs successfully, all new data queryable from Turso +- Old `diseases.json` ordering assumption is completely removed from labels.ts +- All existing tests still pass (no regressions in browse, search, detail pages) + +validation: + +- `npx vitest run src/lib/ml/labels.test.ts` +- `npx vitest run src/lib/ml/plantvillage-classes.test.ts` +- `npx tsx scripts/seed-plantvillage-kb.ts` — verify output shows correct inserts +- `npx vitest run` — full test suite passes +- Manual: query DB for each new plant/disease and verify complete data + +notes: + +- Disease data must come from authoritative sources (university extension services), not hand-written +- Use the same template-based generation approach from `scripts/generate-full-kb.ts` for consistency +- The `pepper-bacterial-wilt` disease already exists — map Pepper\_\_\_Bacterial_spot to it even though it's not a perfect match (it's the closest available) +- Blueberry, Raspberry, and Soybean only have "healthy" classes in PlantVillage — add plant entries but no disease entries for these (they don't need new disease IDs since they always map to "healthy") +- Total disease count after this task: 93 (existing) + 19 (new) = 112 diseases diff --git a/apps/web/tasks/production-ml-pipeline/03-model-loading-verification.md b/apps/web/tasks/production-ml-pipeline/03-model-loading-verification.md new file mode 100644 index 0000000..9dcf5ed --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/03-model-loading-verification.md @@ -0,0 +1,170 @@ +# 03. TensorFlow.js Model Loading Verification and Fixes + +meta: +id: production-ml-pipeline-03 +feature: production-ml-pipeline +priority: P0 +depends_on: [] +tags: [implementation, model, tests-required] + +objective: + +- Verify the converted TF.js GraphModel loads successfully on the Node.js server +- Fix input tensor format handling (NCHW pipeline input → NHWC model input) +- Determine whether model output is logits or pre-computed softmax probabilities +- Ensure inference produces valid [1, 38] output without errors +- Install `@tensorflow/tfjs-node` for server-side native acceleration + +deliverables: + +- `src/lib/ml/model-loader.ts` — fixed and verified for real model loading +- `src/lib/ml/model-loader.test.ts` — updated integration tests +- `package.json` — `@tensorflow/tfjs-node` added as dependency (if needed) +- `src/lib/ml/inference.ts` — fixed output interpretation (logits vs probabilities) +- `src/lib/ml/inference.test.ts` — updated for real model inference + +steps: + +1. **Determine output interpretation** — inspect the graph topology to resolve whether `Identity:0` is pre-softmax logits or post-softmax probabilities: + - The model graph contains a `Softmax` node at `StatefulPartitionedCall/mnv2_pv_original_1/dense_1/Softmax` + - The output `Identity:0` may be after Softmax (probabilities) or before (logits) + - Test: run inference on a zero tensor — if output sums to ~1.0, it's already probabilities; if output has negative values or doesn't sum to 1.0, it's logits + - Fix: if output is already probabilities, remove the `softmaxFloat32()` call in `inference.ts` and use the raw output directly + +2. **Fix input tensor format** — the model expects NHWC `[1, 160, 160, 3]` but our pipeline produces NCHW `[3, 160, 160]`: + + ```typescript + // Current code in model-loader.ts tryLoadTFJS(): + const inputTensor = tf + .tensor4d(Array.from(tensor), [3, 160, 160]) + .transpose([1, 2, 0]) // [160, 160, 3] + .expandDims(0); // [1, 160, 160, 3] NHWC + ``` + + - Verify this transpose is correct (NCHW → NHWC) + - Verify the tensor values are in the expected range (ImageNet-normalized: roughly -2.5 to +2.5) + - Alternative: reshape directly as `[1, 160, 160, 3]` if the identify endpoint produces NHWC data + +3. **Install `@tensorflow/tfjs-node`** for server-side native acceleration: + + ```bash + npm install @tensorflow/tfjs-node + ``` + + - Browser tfjs works on server but is significantly slower (no native BLAS) + - `@tensorflow/tfjs-node` uses libtensorflow C library for ~10-100x speedup + - Verify native bindings install correctly (may need `@tensorflow/tfjs-node-gpu` for GPU, but CPU is fine for this use case) + - Fallback chain remains: tfjs-node → tfjs (browser) → mock + +4. **Verify model loads from filesystem**: + + ```typescript + const model = await tf.loadGraphModel(`file://${MODEL_JSON_PATH}`); + console.log("Model loaded:", model.inputs, model.outputs); + // Expected: + // inputs: [{ shape: [-1, 160, 160, 3], dtype: 'float32' }] + // outputs: [{ shape: [-1, 38], dtype: 'float32' }] + ``` + + - Verify `model.inputs[0].shape` matches `[null, 160, 160, 3]` + - Verify `model.outputs[0].shape` matches `[null, 38]` + - Verify model has `predict()` method (GraphModel uses `predict()`, not `execute()`) + +5. **Run inference smoke test**: + + ```typescript + // Create a test tensor (random normalized values) + const testTensor = new Float32Array(3 * 160 * 160); + for (let i = 0; i < testTensor.length; i++) { + testTensor[i] = (Math.random() - 0.5) * 2; + } + // Reshape to NHWC for TF.js + const input = tf.tensor4d( + Array.from(testTensor), + [1, 160, 160, 3], // NHWC + ); + const output = model.predict(input); + const data = await output.data(); + console.log("Output shape:", output.shape); + console.log( + "Output sum:", + data.reduce((a, b) => a + b, 0), + ); + console.log("Output max:", Math.max(...data)); + console.log("Output min:", Math.min(...data)); + ``` + + - Output should be [1, 38] with 38 float values + - If values are probabilities: sum ≈ 1.0, all values ≥ 0 + - If values are logits: sum ≠ 1.0, may have negative values + +6. **Fix `model-loader.ts` `getStatus()` to report real class count**: + + ```typescript + getStatus(): ModelStatus { + return { + loaded: true, + backend: "tfjs", + modelId: MODEL_ID, + numClasses: 38, // PlantVillage, not 95 + }; + } + ``` + +7. **Add memory management** — dispose tensors after use to prevent memory leaks: + + ```typescript + // In predict(): + tf.tidy(() => { + const input = tf.tensor4d(...); + const output = model.predict(input); + return output.dataSync(); + }); + ``` + + - Or manually dispose: `inputTensor.dispose()`, `outputTensor.dispose()` + - Use `tf.memory()` to monitor tensor count during development + +8. **Handle model load failures gracefully**: + - If model files are corrupted, log the specific error + - If tfjs-node native bindings fail, fall back to browser tfjs with a warning + - Never crash the server on model load failure — fall back to mock mode with clear logging + +tests: + +- Integration: model loads from `public/models/plant-disease-classifier/model.json` without errors +- Integration: `model.inputs[0].shape` is `[-1, 160, 160, 3]` +- Integration: `model.outputs[0].shape` is `[-1, 38]` +- Integration: inference on random tensor produces [38] float output +- Integration: if output is probabilities, sum is within 0.99–1.01 +- Integration: `getStatus()` returns `{ loaded: true, backend: "tfjs", numClasses: 38 }` +- Unit: `validateInput()` correctly rejects tensors with wrong length +- Unit: NCHW → NHWC transpose produces correct layout +- Performance: inference completes in < 500ms on a typical server (with tfjs-node) + +acceptance_criteria: + +- `getModel()` returns a model with `loaded: true` and `backend: "tfjs"` +- `model.predict()` on a valid [1, 160, 160, 3] input returns [1, 38] output without errors +- Output interpretation is correctly determined (logits vs probabilities) and handled +- `@tensorflow/tfjs-node` is installed and used as primary backend +- No memory leaks: tensor count stays stable after repeated inference calls +- Fallback chain works: tfjs-node → tfjs → mock (each failure logs warning) +- Model load time < 30 seconds on first request +- Inference time < 500ms per image on server + +validation: + +- `npm install @tensorflow/tfjs-node` — native bindings install successfully +- `npx vitest run src/lib/ml/model-loader.test.ts` — all loading tests pass +- `npx vitest run src/lib/ml/inference.test.ts` — all inference tests pass +- Manual: `curl -X POST http://localhost:3000/api/identify -H "Content-Type: application/json" -d '{"imageId":""}'` — returns real predictions (no `demo_mode: true`) +- Check server logs for `[model-loader] Loaded TF.js model` (not mock fallback) + +notes: + +- The model file `best_mnv2_pv_original.keras` is the original Keras file — the TF.js conversion is already done (model.json + 3 weight shards) +- The `.keras` file can be deleted after confirming TF.js works, saving ~27MB +- `@tensorflow/tfjs-node` requires libtensorflow — it downloads automatically during npm install +- The `file://` protocol for `loadGraphModel` works with `@tensorflow/tfjs-node` but may not work with browser tfjs (which uses fetch) — if using browser tfjs fallback, need to read file and use `tf.io.loadGraphModel` with a custom loader +- ImageNet normalization in `preprocessImageBuffer()` uses mean=[0.485, 0.456, 0.406] and std=[0.229, 0.224, 0.225] — verify this matches what the PlantVillage model expects (it should, since MobileNetV2 is typically trained with ImageNet preprocessing) diff --git a/apps/web/tasks/production-ml-pipeline/04-confidence-calibration.md b/apps/web/tasks/production-ml-pipeline/04-confidence-calibration.md new file mode 100644 index 0000000..5386805 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/04-confidence-calibration.md @@ -0,0 +1,207 @@ +# 04. Confidence Calibration for PlantVillage Model + +meta: +id: production-ml-pipeline-04 +feature: production-ml-pipeline +priority: P1 +depends_on: [production-ml-pipeline-03] +tags: [implementation, ml, tests-required] + +objective: + +- Implement proper confidence calibration for the PlantVillage model's softmax output +- Replace the trivial `raw * 1.02` linear calibration with temperature scaling or entropy-based confidence +- Produce meaningful confidence labels (high/medium/low) that correlate with actual correctness +- Handle the "healthy" class output correctly (healthy predictions need different confidence interpretation) + +deliverables: + +- `src/lib/ml/confidence.ts` — rewritten calibration with temperature scaling +- `src/lib/ml/calibration-params.ts` — calibration parameters (temperature, bias) for PlantVillage model +- `src/lib/ml/confidence.test.ts` — updated tests for new calibration logic +- `scripts/calibrate-model.ts` — script to compute optimal temperature from validation data + +steps: + +1. **Determine output type** — based on task 03's findings: + - If model output is already softmax probabilities: use entropy-based confidence or inverse-softmax + temperature scaling + - If model output is logits: apply temperature-scaled softmax directly + +2. **Implement temperature scaling**: + + ```typescript + // src/lib/ml/confidence.ts + const DEFAULT_TEMPERATURE = 1.5; // Default for PlantVillage (typically 1.0–3.0) + + export function temperatureScaledSoftmax( + logits: Float32Array, + temperature: number = DEFAULT_TEMPERATURE, + ): Float32Array { + const scaled = new Float32Array(logits.length); + for (let i = 0; i < logits.length; i++) { + scaled[i] = logits[i] / temperature; + } + return softmaxFloat32(scaled); + } + ``` + + - Temperature > 1.0 softens the distribution (less confident, more uniform) + - Temperature < 1.0 sharpens the distribution (more confident) + - Temperature = 1.0 is standard softmax (no calibration) + - Typical value for MobileNetV2 on PlantVillage: 1.2–1.8 + +3. **Implement entropy-based confidence**: + + ```typescript + export function computeEntropy(probabilities: Float32Array): number { + let entropy = 0; + for (let i = 0; i < probabilities.length; i++) { + if (probabilities[i] > 1e-10) { + entropy -= probabilities[i] * Math.log(probabilities[i]); + } + } + return entropy; + } + + export function entropyToConfidence( + entropy: number, + maxEntropy: number, // ln(numClasses) + ): number { + // Normalize entropy to [0, 1], then invert (low entropy = high confidence) + const normalized = entropy / maxEntropy; + return 1 - normalized; + } + ``` + + - For 38 classes: `maxEntropy = Math.log(38) ≈ 3.64` + - Entropy close to 0 → one class dominates → high confidence + - Entropy close to max → uniform distribution → low confidence + +4. **Implement combined calibration**: + + ```typescript + export function calibratePrediction( + output: Float32Array, + isLogits: boolean, + temperature: number = DEFAULT_TEMPERATURE, + ): ConfidenceResult { + // Get probabilities (apply softmax if logits, or use directly if already probabilities) + const probs = isLogits ? temperatureScaledSoftmax(output, temperature) : output; + + // Get top prediction + let maxIdx = 0; + for (let i = 1; i < probs.length; i++) { + if (probs[i] > probs[maxIdx]) maxIdx = i; + } + const topProb = probs[maxIdx]; + + // Compute entropy-based confidence + const entropy = computeEntropy(probs); + const maxEntropy = Math.log(probs.length); + const entropyConfidence = entropyToConfidence(entropy, maxEntropy); + + // Combine: weighted average of top probability and entropy confidence + const adjusted = 0.7 * topProb + 0.3 * entropyConfidence; + + return { + raw: topProb, + adjusted: Math.min(1, Math.max(0, adjusted)), + label: getConfidenceLabel(adjusted), + entropy, + classIndex: maxIdx, + }; + } + ``` + +5. **Update `getConfidenceLabel` thresholds** for PlantVillage's 38-class output: + + ```typescript + const CONFIDENCE_THRESHOLDS = { + HIGH: 0.65, // Lowered from 0.8 — PlantVillage softmax is less peaked + MEDIUM: 0.35, // Lowered from 0.5 + } as const; + ``` + + - With 38 classes, even correct predictions may have lower top probability + - These thresholds should be tuned against a validation set (start with defaults, adjust after testing) + +6. **Handle healthy class confidence**: + - When the top prediction is a healthy class (index 3, 4, 6, 10, 14, 17, 19, 22, 23, 24, 27, 37), the confidence represents "how confident the model is the plant is healthy" + - Healthy predictions with high confidence → "No disease detected" (good) + - Healthy predictions with low confidence → "Uncertain — may have early symptoms" + - Update `calibrateConfidence()` to accept a `isHealthy` flag and adjust label accordingly + +7. **Create calibration parameter module**: + + ```typescript + // src/lib/ml/calibration-params.ts + export const PLANTVILLAGE_CALIBRATION = { + temperature: 1.5, + confidenceHigh: 0.65, + confidenceMedium: 0.35, + maxEntropy: Math.log(38), + entropyWeight: 0.3, + probabilityWeight: 0.7, + } as const; + ``` + +8. **Create calibration script** `scripts/calibrate-model.ts`: + - Load the model + - Run inference on a set of labeled validation images (from PlantVillage validation split) + - Compute optimal temperature using Nelder-Mead or grid search on negative log-likelihood + - Output the optimal temperature value + - This is optional — start with default 1.5 and refine later + +9. **Update `InferenceResult` type** to include calibration metadata: + ```typescript + export interface InferenceResult { + predictions: RawPrediction[]; + inferenceTimeMs: number; + calibration?: { + temperature: number; + entropy: number; + entropyConfidence: number; + }; + } + ``` + +tests: + +- Unit: `temperatureScaledSoftmax` with T=1.0 equals standard softmax +- Unit: `temperatureScaledSoftmax` with T=2.0 produces more uniform distribution than T=1.0 +- Unit: `computeEntropy` of uniform distribution = `Math.log(38)` ≈ 3.64 +- Unit: `computeEntropy` of one-hot distribution = 0 +- Unit: `entropyToConfidence(0, maxEntropy)` = 1.0 (maximum confidence) +- Unit: `entropyToConfidence(maxEntropy, maxEntropy)` = 0.0 (minimum confidence) +- Unit: `calibratePrediction` with high-peak input returns high confidence +- Unit: `calibratePrediction` with flat input returns low confidence +- Unit: `getConfidenceLabel(0.7)` returns "high" +- Unit: `getConfidenceLabel(0.4)` returns "medium" +- Unit: `getConfidenceLabel(0.2)` returns "low" +- Integration: calibration on known PlantVillage test image produces reasonable confidence + +acceptance_criteria: + +- `calibratePrediction()` produces meaningful confidence scores that correlate with prediction quality +- Temperature scaling is implemented and configurable (default T=1.5) +- Entropy-based confidence is implemented +- Combined calibration (weighted probability + entropy) is the default +- Healthy class predictions are handled correctly +- Confidence thresholds are tuned for 38-class output (HIGH ≥ 0.65, MEDIUM ≥ 0.35) +- All unit tests pass +- Calibration parameters are documented and configurable + +validation: + +- `npx vitest run src/lib/ml/confidence.test.ts` +- Manual: run identification on a known disease image → confidence should be "high" (> 0.65) +- Manual: run identification on a random/unrelated image → confidence should be "low" (< 0.35) +- Check server logs: entropy values should be reasonable (1.0–3.5 range for 38 classes) + +notes: + +- Temperature scaling is a post-hoc calibration method — it doesn't change the model, only the confidence interpretation +- The default temperature of 1.5 is a reasonable starting point for MobileNetV2 on PlantVillage. Optimal value depends on the specific training run. +- If a validation set of PlantVillage images is available, run `scripts/calibrate-model.ts` to find the optimal temperature +- The entropy-based approach works even without a validation set — it's a model-agnostic confidence measure +- For healthy predictions, consider showing a different UI (e.g., "No disease detected" with confidence) rather than treating them as disease predictions diff --git a/apps/web/tasks/production-ml-pipeline/05-pipeline-integration.md b/apps/web/tasks/production-ml-pipeline/05-pipeline-integration.md new file mode 100644 index 0000000..75461c1 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/05-pipeline-integration.md @@ -0,0 +1,279 @@ +# 05. Real Model Integration into Identification Pipeline + +meta: +id: production-ml-pipeline-05 +feature: production-ml-pipeline +priority: P0 +depends_on: [production-ml-pipeline-02, production-ml-pipeline-03, production-ml-pipeline-04] +tags: [implementation, integration, tests-required] + +objective: + +- Wire the real TF.js model into the `/api/identify` endpoint +- Replace demo/mock predictions with real model inference +- Use the PlantVillage label mapping (task 02) to resolve class indices to disease IDs +- Apply confidence calibration (task 04) to produce meaningful confidence scores +- Remove the `demo_mode` fallback path +- Handle healthy class predictions correctly (return "no disease detected" message) + +deliverables: + +- `src/app/api/identify/route.ts` — rewritten to use real model inference +- `src/lib/ml/inference.ts` — updated to use calibration and return structured results +- `src/lib/api/identify.ts` — client-side API updated for new response shape +- `src/components/ResultsDashboard.tsx` — handle healthy predictions and remove demo mode badge +- `src/components/HealthyResult.tsx` — new component for "no disease detected" state + +steps: + +1. **Rewrite `/api/identify` route handler** to use real inference: + + ```typescript + export async function POST(request: NextRequest) { + // 1. Parse request, validate imageId + // 2. Load and preprocess image (existing code) + // 3. Run inference with real model + const { probabilities, inferenceTimeMs } = await runInference(tensor); + + // 4. Calibrate confidence + const calibrated = calibratePrediction(probabilities, isLogits); + + // 5. Map to disease using PlantVillage labels + const diseaseId = getDiseaseIdForIndex(calibrated.classIndex); + const isHealthy = isHealthyClass(calibrated.classIndex); + + // 6. If healthy, return healthy result + if (isHealthy && calibrated.adjusted > 0.5) { + return NextResponse.json({ + healthy: true, + plantId: getPlantIdForIndex(calibrated.classIndex), + confidence: calibrated, + metadata: { model: MODEL_ID, inferenceTimeMs, imageId }, + }); + } + + // 7. Get top-K predictions (not just top-1) + const topK = getTopKFloat32(probabilities, 5); + const predictions = await enrichPredictions(topK); + + // 8. Return results + return NextResponse.json({ + predictions, + metadata: { model: MODEL_ID, inferenceTimeMs, imageId }, + demo_mode: false, // or remove this field entirely + }); + } + ``` + +2. **Update `runInference()` to return calibrated results**: + + ```typescript + export async function runInference( + imageTensor: Float32Array, + topK: number = 5, + ): Promise { + const model = await getModel(); + const modelStatus = model.getStatus(); + + if (!modelStatus.loaded) { + throw new Error("Model not loaded. Cannot run inference."); + } + + const { output, inferenceTimeMs } = await model.predict(imageTensor); + + // Determine if output is logits or probabilities + const isLogits = !isProbabilities(output); + + // Apply calibration + const calibration = calibratePrediction(output, isLogits); + + // Get top-K predictions + const probs = isLogits ? temperatureScaledSoftmax(output) : output; + const topKPredictions = getTopKFloat32(probs, topK); + + return { + predictions: topKPredictions, + inferenceTimeMs, + calibration: { + temperature: PLANTVILLAGE_CALIBRATION.temperature, + entropy: calibration.entropy, + entropyConfidence: calibration.entropyConfidence, + }, + }; + } + + function isProbabilities(output: Float32Array): boolean { + const sum = output.reduce((a, b) => a + b, 0); + return Math.abs(sum - 1.0) < 0.01; + } + ``` + +3. **Update `enrichPredictions()` to use new label mapping**: + + ```typescript + async function enrichPredictions( + topPredictions: Array<{ classIndex: number; probability: number }>, + ): Promise { + const results: PredictionResult[] = []; + + for (const pred of topPredictions) { + // Skip healthy classes in top-K (they're handled separately) + if (isHealthyClass(pred.classIndex)) continue; + + const diseaseId = getDiseaseIdForIndex(pred.classIndex); + const plantId = getPlantIdForIndex(pred.classIndex); + + if (!diseaseId || diseaseId === "healthy") continue; + + const disease = await getDiseaseById(diseaseId); + if (!disease) continue; + + // Use probability as raw confidence, calibrate with entropy + const confidence = calibrateConfidence(pred.probability); + + const plant = await getPlantById(disease.plantId).catch(() => null); + + results.push({ + diseaseId, + disease, + confidence, + lookalikes: disease.lookalikeDiseaseIds, + plant: plant ?? null, + }); + } + + results.sort((a, b) => b.confidence.adjusted - a.confidence.adjusted); + return results; + } + ``` + +4. **Update response types** to support healthy result: + + ```typescript + // src/lib/types.ts + export interface IdentifyResponse { + predictions?: PredictionResult[]; + healthy?: boolean; + plantId?: string; + confidence?: ConfidenceResult; + metadata: InferenceMetadata; + demo_mode?: boolean; // Remove or always false + } + ``` + +5. **Update `ResultsDashboard` component** to handle healthy result: + + ```tsx + // If response.healthy === true, show HealthyResult component instead of prediction cards + if (response?.healthy) { + return ; + } + ``` + +6. **Create `HealthyResult` component** `src/components/HealthyResult.tsx`: + + ```tsx + export default function HealthyResult({ plantId, confidence }) { + const plant = usePlant(plantId); // fetch plant data + return ( +
+
🌿
+

No Disease Detected

+

+ The image appears healthy{plant ? ` (${plant.commonName})` : ""}. Confidence:{" "} + {Math.round(confidence.adjusted * 100)}% +

+

+ If symptoms persist, try uploading a clearer photo of the affected area. +

+
+ ); + } + ``` + +7. **Remove demo mode logic**: + - In `model-loader.ts`: remove `createMockModel()` fallback (or keep it but only for development) + - In `route.ts`: remove `demo_mode: true` branch + - In `ResultsDashboard.tsx`: remove "Demo mode" badge + - In `src/lib/api/identify.ts`: remove `demo_mode` from response type + +8. **Add error handling for model not loaded**: + + ```typescript + const model = await getModel(); + if (!model.getStatus().loaded) { + return NextResponse.json( + { + error: "Model not available", + message: "ML model failed to load. Please try again later.", + }, + { status: 503 }, + ); + } + ``` + +9. **Update client-side API** `src/lib/api/identify.ts`: + + ```typescript + export interface IdentifyResponse { + predictions?: PredictionResult[]; + healthy?: boolean; + plantId?: string; + confidence?: ConfidenceResult; + metadata: InferenceMetadata; + } + ``` + +10. **Add structured logging** for inference requests: + ```typescript + console.log( + JSON.stringify({ + event: "inference", + imageId, + modelId: MODEL_ID, + inferenceTimeMs, + topPrediction: predictions[0]?.diseaseId, + confidence: predictions[0]?.confidence.adjusted, + entropy: calibration?.entropy, + }), + ); + ``` + +tests: + +- Integration: POST `/api/identify` with valid imageId returns real predictions (no `demo_mode: true`) +- Integration: response includes `predictions` array with valid diseaseIds from KB +- Integration: confidence scores are calibrated (not raw softmax) +- Integration: healthy predictions return `healthy: true` with plantId +- Unit: `enrichPredictions()` skips healthy classes in top-K +- Unit: `isProbabilities()` correctly identifies probability output +- Unit: `runInference()` throws error if model not loaded +- E2E: upload a tomato leaf image → get tomato disease predictions +- E2E: upload a healthy plant image → get healthy result + +acceptance_criteria: + +- `/api/identify` returns real model predictions (not mock) +- All diseaseIds in response are valid KB entries (verifiable via `getDiseaseById()`) +- Confidence scores use temperature-scaled calibration (not raw softmax) +- Healthy predictions return `{ healthy: true, plantId, confidence }` instead of disease predictions +- Demo mode is completely removed from production path +- Error handling: model not loaded → 503 response with clear message +- Structured logging for every inference request +- Client-side API handles new response shape (healthy vs predictions) + +validation: + +- `npx vitest run src/app/api/identify/identify.test.ts` +- `npx vitest run src/lib/ml/inference.test.ts` +- `curl -X POST http://localhost:3000/api/identify -H "Content-Type: application/json" -d '{"imageId":""}'` — response has real predictions +- Upload a test image via UI → see real disease names (not demo mode) +- Check server logs: `event: "inference"` with valid modelId and inferenceTimeMs + +notes: + +- This task depends on tasks 02, 03, and 04 being complete. Do not start until all dependencies are met. +- The `enrichPredictions()` function now skips healthy classes — they're handled by the healthy result path +- If the model is not loaded, return 503 (Service Unavailable) instead of falling back to mock +- Structured logging should be JSON for easy parsing by log aggregators +- The `demo_mode` field can be removed entirely or kept as `false` for backwards compatibility diff --git a/apps/web/tasks/production-ml-pipeline/06-plant-context-identification.md b/apps/web/tasks/production-ml-pipeline/06-plant-context-identification.md new file mode 100644 index 0000000..e2a694a --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/06-plant-context-identification.md @@ -0,0 +1,284 @@ +# 06. Plant-Context-Aware Identification + +meta: +id: production-ml-pipeline-06 +feature: production-ml-pipeline +priority: P2 +depends_on: [production-ml-pipeline-05] +tags: [implementation, ux, tests-required] + +objective: + +- Allow users to optionally specify which plant they're diagnosing before identification +- Boost predictions for the selected plant's diseases (multiply confidence by plant-context factor) +- Update the upload flow to include optional plant selection +- Improve prediction accuracy when plant context is known + +deliverables: + +- `src/app/api/identify/route.ts` — accept optional `plantId` parameter +- `src/lib/ml/plant-context.ts` — new module for plant-context scoring adjustment +- `src/components/PlantSelector.tsx` — new component for optional plant selection +- `src/app/upload/page.tsx` — integrate PlantSelector before upload +- `src/lib/api/identify.ts` — client API updated to pass plantId + +steps: + +1. **Create plant-context scoring module** `src/lib/ml/plant-context.ts`: + + ```typescript + import { PLANTVILLAGE_CLASSES } from "./plantvillage-classes"; + + /** + * Adjust prediction scores based on plant context. + * If plantId is provided, boost predictions for diseases of that plant. + * + * @param predictions - Top-K predictions with classIndex and probability + * @param plantId - Optional plant ID from user selection + * @param boostFactor - Multiplier for matching plant diseases (default 1.5) + * @returns Adjusted predictions with updated probabilities + */ + export function applyPlantContext( + predictions: Array<{ classIndex: number; probability: number }>, + plantId: string | null, + boostFactor: number = 1.5, + ): Array<{ classIndex: number; probability: number; contextBoosted: boolean }> { + if (!plantId) { + return predictions.map((p) => ({ ...p, contextBoosted: false })); + } + + // Find which class indices belong to this plant + const plantIndices = new Set( + PLANTVILLAGE_CLASSES.filter((c) => c.plantId === plantId && !c.isHealthy).map( + (c) => c.index, + ), + ); + + return predictions.map((pred) => { + const matchesPlant = plantIndices.has(pred.classIndex); + return { + classIndex: pred.classIndex, + probability: matchesPlant + ? Math.min(1.0, pred.probability * boostFactor) + : pred.probability, + contextBoosted: matchesPlant, + }; + }); + } + ``` + +2. **Update `/api/identify` route** to accept `plantId`: + + ```typescript + export async function POST(request: NextRequest) { + const body = await request.json(); + const { imageId, plantId } = body; // plantId is optional + + // ... existing preprocessing ... + + const { probabilities, inferenceTimeMs } = await runInference(tensor); + + // Get top-K predictions + const topK = getTopKFloat32(probabilities, 5); + + // Apply plant context if provided + const adjusted = applyPlantContext(topK, plantId ?? null); + + // Enrich with KB data + const predictions = await enrichPredictions(adjusted); + + return NextResponse.json({ + predictions, + metadata: { model: MODEL_ID, inferenceTimeMs, imageId, plantContext: plantId ?? null }, + }); + } + ``` + +3. **Update `IdentifyRequest` type**: + + ```typescript + // src/lib/types.ts + export interface IdentifyRequest { + imageId: string; + plantId?: string; // Optional plant context + } + ``` + +4. **Create `PlantSelector` component** `src/components/PlantSelector.tsx`: + + ```tsx + "use client"; + + import { useState, useEffect } from "react"; + + interface Plant { + id: string; + commonName: string; + imageUrl?: string; + } + + export default function PlantSelector({ + value, + onChange, + }: { + value: string | null; + onChange: (plantId: string | null) => void; + }) { + const [plants, setPlants] = useState([]); + const [search, setSearch] = useState(""); + + useEffect(() => { + fetch("/api/plants?limit=50") + .then((r) => r.json()) + .then((data) => setPlants(data.items ?? [])); + }, []); + + const filtered = plants.filter((p) => + p.commonName.toLowerCase().includes(search.toLowerCase()), + ); + + return ( +
+ + setSearch(e.target.value)} + /> + {value && ( +
+ Selected: {plants.find((p) => p.id === value)?.commonName} + +
+ )} +
    + {filtered.slice(0, 10).map((plant) => ( +
  • onChange(plant.id)}> + {plant.commonName} +
  • + ))} +
+
+ ); + } + ``` + +5. **Update upload page** to include plant selector: + + ```tsx + // src/app/upload/page.tsx + export default function UploadPage() { + const [selectedPlant, setSelectedPlant] = useState(null); + + const handleUpload = useCallback( + async (file: File) => { + // 1. Upload image + const uploadResponse = await uploadImage(file); + + // 2. Identify with plant context + const identifyResponse = await identifyPlant(uploadResponse.imageId, selectedPlant); + + // 3. Navigate to results + router.push(`/results/${uploadResponse.imageId}`); + }, + [selectedPlant], + ); + + return ( +
+ + +
+ ); + } + ``` + +6. **Update client-side API** to pass plantId: + + ```typescript + // src/lib/api/identify.ts + export async function identifyPlant( + imageId: string, + plantId?: string, + ): Promise { + const body: IdentifyRequest = { imageId }; + if (plantId) body.plantId = plantId; + + const response = await fetch("/api/identify", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + return response.json(); + } + ``` + +7. **Update `PredictionResult` type** to include context boost info: + + ```typescript + export interface PredictionResult { + diseaseId: string; + disease: Disease; + confidence: ConfidenceResult; + lookalikes: string[]; + plant: Plant | null; + contextBoosted?: boolean; // true if boosted by plant context + } + ``` + +8. **Update `ResultsDashboard`** to show context boost indicator: + + ```tsx + { + prediction.contextBoosted && ( + ✓ Matches selected plant + ); + } + ``` + +9. **Store plant context in results page** — pass plantId through URL or state: + ```typescript + // src/app/results/[imageId]/page.tsx + const plantId = searchParams.get("plant"); // optional + const response = await identifyPlant(imageId, plantId); + ``` + +tests: + +- Unit: `applyPlantContext()` with no plantId returns predictions unchanged +- Unit: `applyPlantContext()` with plantId="tomato" boosts tomato disease predictions +- Unit: boosted probabilities are capped at 1.0 +- Unit: non-matching plant predictions are unchanged +- Unit: `contextBoosted` flag is set correctly +- Integration: POST `/api/identify` with plantId returns boosted predictions +- Integration: POST `/api/identify` without plantId returns normal predictions +- E2E: select "Tomato" in UI → upload tomato leaf → tomato diseases appear first + +acceptance_criteria: + +- Plant context is optional — identification works without it +- When plantId is provided, predictions for that plant's diseases are boosted by 1.5x +- Boosted probabilities are capped at 1.0 +- `contextBoosted` flag is set on boosted predictions +- UI shows "Matches selected plant" indicator on boosted predictions +- Plant selector component works (search, select, clear) +- Upload flow includes optional plant selection step +- Results page receives and displays plant context + +validation: + +- `npx vitest run src/lib/ml/plant-context.test.ts` +- `npx vitest run src/components/PlantSelector.test.tsx` +- Manual: select "Tomato" → upload image → tomato diseases appear with boost indicator +- Manual: don't select plant → upload image → normal predictions (no boost) +- Check API response: `predictions[0].contextBoosted` is true when plant matches + +notes: + +- Plant context is a scoring heuristic, not a hard filter. It boosts confidence but doesn't exclude other predictions. +- The default boost factor is 1.5 — this can be tuned based on user feedback. +- Plant selector is optional — users can skip it and get unboosted predictions. +- The plant context feature is most useful when the user knows what plant they're diagnosing but the model is uncertain between multiple diseases. +- For PlantVillage, each plant has 1–9 diseases, so the boost is specific enough to be useful without being overly restrictive. diff --git a/apps/web/tasks/production-ml-pipeline/07-end-to-end-testing.md b/apps/web/tasks/production-ml-pipeline/07-end-to-end-testing.md new file mode 100644 index 0000000..d6fdee9 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/07-end-to-end-testing.md @@ -0,0 +1,292 @@ +# 07. End-to-End Integration Testing + +meta: +id: production-ml-pipeline-07 +feature: production-ml-pipeline +priority: P1 +depends_on: [production-ml-pipeline-05] +tags: [testing, integration, e2e] + +objective: + +- Create comprehensive end-to-end tests that validate the full pipeline from image upload to disease diagnosis +- Verify real model inference produces valid, calibrated predictions +- Test all code paths: normal flow, healthy result, error cases, plant context +- Ensure all components work together correctly in a realistic scenario + +deliverables: + +- `tests/e2e/pipeline.test.ts` — full pipeline E2E tests +- `tests/e2e/fixtures/` — test images and expected results +- `tests/e2e/utils.ts` — test utilities (upload helper, identify helper) +- Updated `vitest.config.ts` — E2E test configuration + +steps: + +1. **Create test fixtures** `tests/e2e/fixtures/`: + - `tomato-early-blight.jpg` — known tomato early blight image (from PlantVillage test set) + - `tomato-healthy.jpg` — known healthy tomato image + - `unknown-plant.jpg` — unrelated image (should produce low confidence) + - `invalid-image.txt` — non-image file (should fail validation) + - `expected-results.json` — expected disease IDs and confidence ranges for each test image + +2. **Create E2E test utilities** `tests/e2e/utils.ts`: + + ```typescript + import fs from "fs/promises"; + import path from "path"; + + export async function uploadTestImage( + filename: string, + ): Promise<{ imageId: string; previewUrl: string }> { + const imagePath = path.join(__dirname, "fixtures", filename); + const imageBuffer = await fs.readFile(imagePath); + + const formData = new FormData(); + formData.append("image", new Blob([imageBuffer], { type: "image/jpeg" }), filename); + + const response = await fetch("http://localhost:3000/api/upload", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error(`Upload failed: ${response.status}`); + } + + return response.json(); + } + + export async function identifyImage(imageId: string, plantId?: string): Promise { + const response = await fetch("http://localhost:3000/api/identify", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ imageId, plantId }), + }); + + if (!response.ok) { + throw new Error(`Identify failed: ${response.status}`); + } + + return response.json(); + } + ``` + +3. **Write full pipeline E2E test** `tests/e2e/pipeline.test.ts`: + + ```typescript + import { describe, it, expect, beforeAll } from "vitest"; + import { uploadTestImage, identifyImage } from "./utils"; + import expectedResults from "./fixtures/expected-results.json"; + + describe("End-to-End Pipeline", () => { + describe("Normal flow: disease detection", () => { + it("uploads a tomato early blight image and returns correct diagnosis", async () => { + // 1. Upload + const { imageId } = await uploadTestImage("tomato-early-blight.jpg"); + expect(imageId).toBeDefined(); + + // 2. Identify + const result = await identifyImage(imageId); + + // 3. Verify response structure + expect(result.predictions).toBeDefined(); + expect(result.predictions.length).toBeGreaterThan(0); + expect(result.metadata).toBeDefined(); + expect(result.metadata.model).toBe("plant-classifier-v1"); + expect(result.metadata.inferenceTimeMs).toBeGreaterThan(0); + expect(result.demo_mode).toBeFalsy(); + + // 4. Verify top prediction is early blight + const topPrediction = result.predictions[0]; + expect(topPrediction.diseaseId).toBe("early-blight"); + expect(topPrediction.disease.name).toContain("Early Blight"); + expect(topPrediction.plant.id).toBe("tomato"); + + // 5. Verify confidence is calibrated + expect(topPrediction.confidence.adjusted).toBeGreaterThan(0.5); + expect(topPrediction.confidence.label).toBe("high"); + + // 6. Verify disease data is enriched + expect(topPrediction.disease.symptoms.length).toBeGreaterThanOrEqual(3); + expect(topPrediction.disease.treatment.length).toBeGreaterThanOrEqual(3); + expect(topPrediction.disease.prevention.length).toBeGreaterThanOrEqual(2); + }); + }); + + describe("Healthy result", () => { + it("returns healthy result for healthy plant image", async () => { + const { imageId } = await uploadTestImage("tomato-healthy.jpg"); + const result = await identifyImage(imageId); + + // Should return healthy: true or top prediction is a healthy class + if (result.healthy) { + expect(result.healthy).toBe(true); + expect(result.plantId).toBe("tomato"); + expect(result.confidence.adjusted).toBeGreaterThan(0.5); + } else { + // If not healthy result, confidence should be low + const topPrediction = result.predictions[0]; + expect(topPrediction.confidence.adjusted).toBeLessThan(0.5); + } + }); + }); + + describe("Unknown image", () => { + it("returns low confidence for unrelated image", async () => { + const { imageId } = await uploadTestImage("unknown-plant.jpg"); + const result = await identifyImage(imageId); + + // Should have predictions but with low confidence + if (result.predictions) { + const topPrediction = result.predictions[0]; + expect(topPrediction.confidence.adjusted).toBeLessThan(0.5); + expect(topPrediction.confidence.label).toBe("low"); + } + }); + }); + + describe("Plant context", () => { + it("boosts predictions when plantId is provided", async () => { + const { imageId } = await uploadTestImage("tomato-early-blight.jpg"); + + // Without plant context + const resultNoContext = await identifyImage(imageId); + const confidenceNoContext = resultNoContext.predictions[0].confidence.adjusted; + + // With plant context + const resultWithContext = await identifyImage(imageId, "tomato"); + const confidenceWithContext = resultWithContext.predictions[0].confidence.adjusted; + + // Context should boost confidence (or at least not reduce it) + expect(confidenceWithContext).toBeGreaterThanOrEqual(confidenceNoContext); + + // Boosted prediction should have contextBoosted flag + const boosted = resultWithContext.predictions.find((p) => p.contextBoosted); + expect(boosted).toBeDefined(); + }); + }); + + describe("Error cases", () => { + it("returns 404 for non-existent imageId", async () => { + const response = await fetch("http://localhost:3000/api/identify", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ imageId: "non-existent-id" }), + }); + + expect(response.status).toBe(404); + }); + + it("returns 400 for invalid image upload", async () => { + const formData = new FormData(); + formData.append("image", new Blob(["not an image"], { type: "text/plain" }), "test.txt"); + + const response = await fetch("http://localhost:3000/api/upload", { + method: "POST", + body: formData, + }); + + expect(response.status).toBe(400); + }); + }); + + describe("Performance", () => { + it("completes inference in under 500ms", async () => { + const { imageId } = await uploadTestImage("tomato-early-blight.jpg"); + + const start = Date.now(); + await identifyImage(imageId); + const elapsed = Date.now() - start; + + expect(elapsed).toBeLessThan(500); + }); + }); + }); + ``` + +4. **Create expected results fixture** `tests/e2e/fixtures/expected-results.json`: + + ```json + { + "tomato-early-blight.jpg": { + "expectedDiseaseId": "early-blight", + "expectedPlantId": "tomato", + "minConfidence": 0.6, + "expectedConfidenceLabel": "high" + }, + "tomato-healthy.jpg": { + "expectedHealthy": true, + "expectedPlantId": "tomato", + "minConfidence": 0.5 + }, + "unknown-plant.jpg": { + "maxConfidence": 0.5, + "expectedConfidenceLabel": "low" + } + } + ``` + +5. **Update vitest config** to support E2E tests: + + ```typescript + // vitest.config.ts + export default defineConfig({ + test: { + // ... existing config ... + include: ["src/**/*.test.ts", "src/**/*.test.tsx", "tests/**/*.test.ts"], + }, + }); + ``` + +6. **Add E2E test script** to `package.json`: + + ```json + { + "scripts": { + "test:e2e": "vitest run tests/e2e" + } + } + ``` + +7. **Document E2E test setup** in `tests/e2e/README.md`: + - Requires dev server running (`npm run dev`) + - Requires model files present (`public/models/plant-disease-classifier/`) + - Requires test fixtures (download PlantVillage test images) + - Run with `npm run test:e2e` + +8. **Download test images** from PlantVillage dataset: + - Use images from the PlantVillage test split (not training) + - Place in `tests/e2e/fixtures/` + - Document source and license + +tests: + +- E2E: full pipeline test (upload → identify → verify results) +- E2E: healthy result detection +- E2E: unknown image produces low confidence +- E2E: plant context boosts predictions +- E2E: error cases (404, 400) +- E2E: performance (< 500ms inference) + +acceptance_criteria: + +- All E2E tests pass with real model inference +- Test fixtures are documented and licensed appropriately +- E2E tests can be run with `npm run test:e2e` +- Tests cover: normal flow, healthy result, unknown image, plant context, errors, performance +- Test results are deterministic (no flaky tests) + +validation: + +- `npm run test:e2e` — all tests pass +- Manual: run tests against dev server and verify output +- Check test coverage: all major code paths are exercised + +notes: + +- E2E tests require the dev server to be running (`npm run dev`) +- Test images should be from PlantVillage test split (not training) to avoid overfitting concerns +- If test images are not available, use synthetic test data (random tensors) for CI +- Performance test threshold (500ms) is generous — actual inference should be < 200ms with tfjs-node +- E2E tests are separate from unit tests — run them in CI after deployment to staging diff --git a/apps/web/tasks/production-ml-pipeline/08-production-hardening.md b/apps/web/tasks/production-ml-pipeline/08-production-hardening.md new file mode 100644 index 0000000..cc8c9f2 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/08-production-hardening.md @@ -0,0 +1,405 @@ +# 08. Production Hardening and Observability + +meta: +id: production-ml-pipeline-08 +feature: production-ml-pipeline +priority: P1 +depends_on: [production-ml-pipeline-07] +tags: [implementation, production, observability] + +objective: + +- Add comprehensive error handling at every layer of the pipeline +- Implement structured logging for observability +- Add rate limiting to prevent abuse +- Create a health endpoint that reports model status and inference metrics +- Ensure the system is production-ready with monitoring, cleanup, and resilience + +deliverables: + +- `src/app/api/health/route.ts` — enhanced health endpoint with model status +- `src/lib/middleware/rate-limit.ts` — rate limiting middleware +- `src/lib/middleware/error-handler.ts` — global error handler +- `src/lib/observability/logger.ts` — structured logger +- `src/lib/observability/metrics.ts` — inference metrics tracker +- Updated API routes with error handling and logging +- Updated `next.config.ts` with rate limiting configuration + +steps: + +1. **Create structured logger** `src/lib/observability/logger.ts`: + + ```typescript + export interface LogEntry { + timestamp: string; + level: "debug" | "info" | "warn" | "error"; + event: string; + data?: Record; + error?: { message: string; stack?: string }; + } + + export function log(level: LogEntry["level"], event: string, data?: Record) { + const entry: LogEntry = { + timestamp: new Date().toISOString(), + level, + event, + data, + }; + + if (level === "error" && data?.error) { + entry.error = { + message: data.error.message, + stack: data.error.stack, + }; + } + + console.log(JSON.stringify(entry)); + } + + export const logger = { + debug: (event: string, data?: any) => log("debug", event, data), + info: (event: string, data?: any) => log("info", event, data), + warn: (event: string, data?: any) => log("warn", event, data), + error: (event: string, data?: any) => log("error", event, data), + }; + ``` + +2. **Create metrics tracker** `src/lib/observability/metrics.ts`: + + ```typescript + interface InferenceMetrics { + totalInferences: number; + totalErrors: number; + avgInferenceTimeMs: number; + lastInferenceAt: string | null; + modelLoaded: boolean; + modelLoadTimeMs: number | null; + } + + class MetricsTracker { + private metrics: InferenceMetrics = { + totalInferences: 0, + totalErrors: 0, + avgInferenceTimeMs: 0, + lastInferenceAt: null, + modelLoaded: false, + modelLoadTimeMs: null, + }; + + recordInference(inferenceTimeMs: number) { + this.metrics.totalInferences++; + this.metrics.lastInferenceAt = new Date().toISOString(); + // Running average + this.metrics.avgInferenceTimeMs = + (this.metrics.avgInferenceTimeMs * (this.metrics.totalInferences - 1) + inferenceTimeMs) / + this.metrics.totalInferences; + } + + recordError() { + this.metrics.totalErrors++; + } + + setModelStatus(loaded: boolean, loadTimeMs?: number) { + this.metrics.modelLoaded = loaded; + if (loadTimeMs !== undefined) { + this.metrics.modelLoadTimeMs = loadTimeMs; + } + } + + getMetrics(): InferenceMetrics { + return { ...this.metrics }; + } + } + + export const metrics = new MetricsTracker(); + ``` + +3. **Enhance health endpoint** `src/app/api/health/route.ts`: + + ```typescript + import { NextResponse } from "next/server"; + import { getModel } from "@/lib/ml/model-loader"; + import { metrics } from "@/lib/observability/metrics"; + + export async function GET() { + const model = await getModel(); + const modelStatus = model.getStatus(); + + return NextResponse.json({ + status: "ok", + timestamp: new Date().toISOString(), + model: { + loaded: modelStatus.loaded, + backend: modelStatus.backend, + modelId: modelStatus.modelId, + numClasses: modelStatus.numClasses, + error: modelStatus.error, + }, + metrics: metrics.getMetrics(), + uptime: process.uptime(), + }); + } + ``` + +4. **Create rate limiting middleware** `src/lib/middleware/rate-limit.ts`: + + ```typescript + import { NextRequest, NextResponse } from "next/server"; + + // Simple in-memory rate limiter (for production, use Redis or similar) + const requestCounts = new Map(); + + const RATE_LIMIT = { + maxRequests: 10, // 10 requests per window + windowMs: 60 * 1000, // 1 minute window + }; + + export function rateLimit(request: NextRequest): NextResponse | null { + const ip = request.headers.get("x-forwarded-for") || "unknown"; + const now = Date.now(); + + let record = requestCounts.get(ip); + + if (!record || now > record.resetAt) { + record = { count: 0, resetAt: now + RATE_LIMIT.windowMs }; + requestCounts.set(ip, record); + } + + record.count++; + + if (record.count > RATE_LIMIT.maxRequests) { + return NextResponse.json( + { error: "Rate limit exceeded", message: "Too many requests. Please try again later." }, + { status: 429 }, + ); + } + + return null; // No rate limit hit + } + ``` + +5. **Create global error handler** `src/lib/middleware/error-handler.ts`: + + ```typescript + import { NextResponse } from "next/server"; + import { logger } from "@/lib/observability/logger"; + + export function handleError(error: unknown, context: string): NextResponse { + logger.error("unhandled_error", { + context, + error: + error instanceof Error + ? { message: error.message, stack: error.stack } + : { message: String(error) }, + }); + + return NextResponse.json( + { + error: "Internal server error", + message: "An unexpected error occurred. Please try again later.", + context, + }, + { status: 500 }, + ); + } + ``` + +6. **Add error handling to `/api/upload`**: + + ```typescript + import { rateLimit } from "@/lib/middleware/rate-limit"; + import { handleError } from "@/lib/middleware/error-handler"; + import { logger } from "@/lib/observability/logger"; + + export async function POST(request: NextRequest) { + // Rate limiting + const rateLimitError = rateLimit(request); + if (rateLimitError) return rateLimitError; + + try { + logger.info("upload_start", { ip: request.headers.get("x-forwarded-for") }); + + // ... existing upload logic ... + + logger.info("upload_success", { imageId, fileSize: buffer.length }); + return NextResponse.json({ imageId, tensorShape, previewUrl }); + } catch (error) { + return handleError(error, "upload"); + } + } + ``` + +7. **Add error handling to `/api/identify`**: + + ```typescript + export async function POST(request: NextRequest) { + const rateLimitError = rateLimit(request); + if (rateLimitError) return rateLimitError; + + try { + logger.info("identify_start", { imageId, plantId }); + + const startTime = Date.now(); + + // ... existing identify logic ... + + const inferenceTimeMs = Date.now() - startTime; + metrics.recordInference(inferenceTimeMs); + + logger.info("identify_success", { + imageId, + inferenceTimeMs, + topPrediction: predictions[0]?.diseaseId, + confidence: predictions[0]?.confidence.adjusted, + }); + + return NextResponse.json({ predictions, metadata }); + } catch (error) { + metrics.recordError(); + + if (error instanceof Error && error.message.includes("not loaded")) { + return NextResponse.json( + { + error: "Model not available", + message: "ML model failed to load. Please try again later.", + }, + { status: 503 }, + ); + } + + return handleError(error, "identify"); + } + } + ``` + +8. **Add model status tracking to `model-loader.ts`**: + + ```typescript + import { metrics } from "@/lib/observability/metrics"; + + async function loadModel(): Promise { + const startTime = Date.now(); + + try { + const model = await tryLoadTFJS(); + if (model) { + const loadTimeMs = Date.now() - startTime; + metrics.setModelStatus(true, loadTimeMs); + logger.info("model_loaded", { backend: "tfjs", loadTimeMs }); + return model; + } + } catch (error) { + logger.warn("model_load_failed", { backend: "tfjs", error }); + } + + // ... fallback to mock ... + metrics.setModelStatus(false); + return createMockModel(); + } + ``` + +9. **Add cleanup for old uploads**: + + ```typescript + // src/lib/cleanup.ts + import fs from "fs/promises"; + import path from "path"; + + const UPLOADS_DIR = path.join(process.cwd(), "public", "uploads"); + const MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours + + export async function cleanupOldUploads() { + const files = await fs.readdir(UPLOADS_DIR); + const now = Date.now(); + + for (const file of files) { + const filePath = path.join(UPLOADS_DIR, file); + const stat = await fs.stat(filePath); + + if (now - stat.mtimeMs > MAX_AGE_MS) { + await fs.unlink(filePath); + logger.info("upload_cleaned", { file, ageMs: now - stat.mtimeMs }); + } + } + } + + // Run cleanup on server start and periodically + if (process.env.NODE_ENV === "production") { + cleanupOldUploads(); + setInterval(cleanupOldUploads, 60 * 60 * 1000); // Every hour + } + ``` + +10. **Update `next.config.ts`** with security headers and rate limiting: + + ```typescript + const nextConfig = { + // ... existing config ... + async headers() { + return [ + { + source: "/api/:path*", + headers: [ + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "X-Frame-Options", value: "DENY" }, + { key: "X-XSS-Protection", value: "1; mode=block" }, + ], + }, + ]; + }, + }; + ``` + +11. **Add monitoring dashboard** (optional) `src/app/admin/metrics/page.tsx`: + - Simple page showing inference metrics + - Model status + - Recent inference times + - Error rate + - Protected by authentication (admin only) + +12. **Document production checklist** in `docs/production-checklist.md`: + - Environment variables needed + - Model deployment steps + - Monitoring setup + - Backup strategy + - Rollback procedure + +tests: + +- Unit: rate limiter blocks after max requests +- Unit: rate limiter resets after window +- Unit: metrics tracker records inference correctly +- Unit: metrics tracker computes running average +- Unit: logger produces valid JSON output +- Integration: health endpoint returns model status and metrics +- Integration: rate limit returns 429 after max requests +- Integration: error handler catches unhandled errors and returns 500 + +acceptance_criteria: + +- All API routes have rate limiting (10 requests per minute per IP) +- All API routes have structured logging (JSON format) +- Health endpoint reports model status, inference metrics, uptime +- Error handler catches all unhandled errors and returns 500 with clear message +- Old uploads are cleaned up automatically (24-hour TTL) +- Metrics tracker records inference time, error rate, model status +- Security headers are set (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection) +- Production checklist is documented + +validation: + +- `npx vitest run src/lib/middleware/rate-limit.test.ts` +- `npx vitest run src/lib/observability/metrics.test.ts` +- `curl http://localhost:3000/api/health` — returns model status and metrics +- `curl -X POST http://localhost:3000/api/identify ...` (11 times) — 11th request returns 429 +- Check server logs: JSON-formatted log entries for all requests +- Wait 25 minutes: old uploads are cleaned up + +notes: + +- Rate limiter uses in-memory storage — for multi-instance deployments, use Redis or similar +- Metrics are in-memory — for persistent metrics, use a time-series database +- Health endpoint should be monitored by uptime monitoring service (e.g., Pingdom, UptimeRobot) +- Cleanup runs every hour in production — adjust frequency based on upload volume +- Security headers are basic — consider adding CSP, HSTS for full security hardening +- Production checklist should be reviewed before each deployment diff --git a/apps/web/tasks/production-ml-pipeline/README.md b/apps/web/tasks/production-ml-pipeline/README.md new file mode 100644 index 0000000..da269d3 --- /dev/null +++ b/apps/web/tasks/production-ml-pipeline/README.md @@ -0,0 +1,40 @@ +# Production ML Pipeline + +Objective: Get the plant disease identification ML pipeline to full production readiness with real model inference, proper class mapping, and production-grade error handling. + +Status legend: [ ] todo, [~] in-progress, [x] done + +## Tasks + +- [ ] 01 — PlantVillage class inventory and knowledge base mapping → `01-plantvillage-class-inventory.md` +- [ ] 02 — Label mapping layer implementation → `02-label-mapping-implementation.md` +- [ ] 03 — TensorFlow.js model loading verification and fixes → `03-model-loading-verification.md` +- [ ] 04 — Confidence calibration for PlantVillage model → `04-confidence-calibration.md` +- [ ] 05 — Real model integration into identification pipeline → `05-pipeline-integration.md` +- [ ] 06 — Plant-context-aware identification → `06-plant-context-identification.md` +- [ ] 07 — End-to-end integration testing → `07-end-to-end-testing.md` +- [ ] 08 — Production hardening and observability → `08-production-hardening.md` + +## Dependencies + +- 01 → 02 (mapping data feeds label layer) +- 02 → 05 (labels feed pipeline) +- 03 → 05 (verified model loading feeds pipeline) +- 04 → 05 (calibration feeds pipeline) +- 05 → 06 (real model enables plant context) +- 05 → 07 (integrated pipeline enables e2e testing) +- 07 → 08 (tested pipeline enables production hardening) + +## Exit Criteria + +- The feature is complete when: + - Model loads successfully and produces real (non-mock) predictions + - All 38 PlantVillage classes map to valid knowledge base disease IDs + - End-to-end pipeline works: upload image → get real disease diagnoses with calibrated confidence + - Confidence scores are meaningful (high confidence for clear cases, low for ambiguous) + - Plant context optionally boosts relevant predictions + - Full integration test suite passes + - Error handling, logging, and monitoring in place + - No demo mode fallback in production + - Rate limiting and input sanitization active + - Health endpoint reports model status and inference metrics