<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>PlantLab.ai | Blog</title>
    <link>https://blog.plantlab.ai/</link>
    <description>AI based plant health diagnosis</description>
    <pubDate>Tue, 14 Jul 2026 03:34:47 +0000</pubDate>
    <image>
      <url>https://i.snap.as/7V8pjnV9.jpeg</url>
      <title>PlantLab.ai | Blog</title>
      <link>https://blog.plantlab.ai/</link>
    </image>
    <item>
      <title>Build an Autonomous Plant Health Monitor with Home Assistant</title>
      <link>https://blog.plantlab.ai/home-assistant-plant-monitoring?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[What You&#39;ll Build&#xA;&#xA;A camera watches your plants on a schedule. Each photo goes to an AI trained only on cannabis, and Home Assistant turns the answer into sensors you can automate against. Something looks off, your phone buzzes with the diagnosis and the photo. Everything&#39;s fine, it stays quiet.&#xA;&#xA;Setup runs about 20 minutes. The cost is a camera you probably already own plus PlantLab&#39;s free tier - three diagnoses a day, no card. No soldering, no standalone Python scripts. A Home Assistant integration and some YAML.&#xA;&#xA;A healthy flowering cannabis plant next to a Home Assistant dashboard card showing PlantLab health, growth stage, and reliability sensors&#xA;&#xA;!--more--&#xA;&#xA;Your eyes are good. They&#39;re also asleep at 6 AM, and they&#39;ve never once compared today&#39;s leaf color against yesterday&#39;s with any real consistency. A camera bolted to a tent pole doesn&#39;t get distracted and has never talked itself into &#34;it&#39;s probably fine&#34; at 11 PM. That&#39;s the whole pitch.&#xA;&#xA;If you run Node-RED instead of native HA automations, there&#39;s a companion walkthrough for that. This one stays inside Home Assistant.&#xA;&#xA;---&#xA;&#xA;Prerequisites&#xA;&#xA;Home Assistant running - any install method&#xA;A camera HA can snapshot: IP cam, Frigate, ESP32-CAM, Wyze with RTSP, Reolink, Tapo, anything with a camera. entity&#xA;A PlantLab account - free at plantlab.ai; grab your API key from the dashboard&#xA;HACS - optional, but it makes the install one click&#xA;Basic YAML comfort. If you&#39;ve edited configuration.yaml, you&#39;re set.&#xA;&#xA;Camera positioning: shoot the canopy from above or a slight angle. Overhead gives the best coverage. Avoid shooting through blurple LEDs - the model needs real leaf color, not everything tinted purple. If your lights are blurple, run the check during a lights-off window or use the camera&#39;s white LED.&#xA;&#xA;---&#xA;&#xA;Step 1: Install the PlantLab Integration&#xA;&#xA;Via HACS (recommended)&#xA;&#xA;Open HACS in the sidebar&#xA;Search PlantLab in Integrations&#xA;Click Download&#xA;Restart Home Assistant&#xA;&#xA;Manual install&#xA;&#xA;SSH into your HA machine&#xA;cd /config/customcomponents/&#xA;git clone https://github.com/plantlab-ai/home-assistant-plantlab.git plantlabtemp&#xA;mv plantlabtemp/customcomponents/plantlab .&#xA;rm -rf plantlabtemp&#xA;&#xA;Restart HA after copying.&#xA;&#xA;Configure&#xA;&#xA;Settings   Devices &amp; Services&#xA;+ Add Integration&#xA;Search PlantLab&#xA;Paste your API key&#xA;Done - you get a PlantLab device with a set of sensors.&#xA;&#xA;No YAML for the integration itself. The config flow handles it.&#xA;&#xA;---&#xA;&#xA;Step 2: Meet Your New Sensors&#xA;&#xA;After setup you get these entities automatically:&#xA;&#xA;| Entity | What it shows | Example state |&#xA;|--------|--------------|---------------|&#xA;| sensor.plantlabhealth | Overall verdict | unhealthy |&#xA;| sensor.plantlabconditions | Top condition | Nitrogen Deficiency |&#xA;| sensor.plantlabpests | Top pest | Spider Mites |&#xA;| sensor.plantlabgrowthstage | Current stage | vegetative |&#xA;| sensor.plantlabnutrientanalysis | Mulder&#39;s Chart hypothesis | potassiumexcess |&#xA;| sensor.plantlabreliabilityscore | How much to trust this call (0-100%) | 82.0 |&#xA;| sensor.plantlabplantcount | Plants detected in the frame | 1 |&#xA;| binarysensor.plantlabproblem | Simple on/off | on when something&#39;s wrong |&#xA;&#xA;One thing worth flagging up front: the condition and pest sensors report the display name - Nitrogen Deficiency, not nitrogendeficiency. That matters when you trigger automations on their state. The raw snakecase classid lives in the sensor&#39;s attributes and in the service response, which is what you&#39;ll actually key automations off of below.&#xA;&#xA;Everything reads &#34;Unknown&#34; until the first diagnosis runs. After a check:&#xA;&#xA;PlantLab sensor states in Home Assistant showing nitrogen deficiency detected&#xA;&#xA;---&#xA;&#xA;Step 3: The Daily Health Check&#xA;&#xA;Runs once a day, grabs a photo, sends it to PlantLab, and pings you only if there&#39;s a problem. Quiet when fine, loud when not.&#xA;&#xA;automation:&#xA;  alias: &#34;Plant Health Check - Morning&#34;&#xA;    trigger:&#xA;      platform: time&#xA;        at: &#34;08:00:00&#34;&#xA;    action:&#xA;      # Capture a snapshot&#xA;      action: camera.snapshot&#xA;        target:&#xA;          entityid: camera.growtent    # your camera entity&#xA;        data:&#xA;          filename: /config/www/plantcheck.jpg&#xA;&#xA;      # Give the file a moment to write&#xA;      delay:&#xA;          seconds: 3&#xA;&#xA;      # Run the diagnosis&#xA;      action: plantlab.diagnose&#xA;        data:&#xA;          imagepath: /config/www/plantcheck.jpg&#xA;        responsevariable: result&#xA;&#xA;      # Alert only if the first plant comes back unhealthy&#xA;      if:   {{ result.results | count   0&#xA;             and result.results[0].ishealthy == false }}&#xA;        then:&#xA;          action: notify.mobileappyourphone&#xA;            data:&#xA;              title: &#34;Plant Issue Detected&#34;&#xA;              message:   {{ result.results[0].conditions[0].displayname }}&#xA;                detected ({{ (result.results[0].conditions[0].confidence  100) | round }}% confidence).&#xA;                Growth stage: {{ result.results[0].growthstage }}.&#xA;              data:&#xA;                image: /local/plantcheck.jpg&#xA;&#xA;One structural thing to know: PlantLab returns one result per detected plant under results. result.results[0] is the first plant, which is why the health check, the condition, and the confidence all read off results[0] and not the top level. More on multi-plant in a moment.&#xA;&#xA;Why morning? You catch overnight problems before the lights-on period drives symptoms further, and the first hour of the light cycle gives a neutral photo without heat-droop.&#xA;&#xA;Skip the file - snapshot from the camera directly&#xA;&#xA;Don&#39;t want files on disk? Point the service at the camera entity:&#xA;&#xA;      action: plantlab.diagnose&#xA;        data:&#xA;          entityid: camera.growtent&#xA;        responsevariable: result&#xA;&#xA;Simpler, but you lose the photo to attach to the notification. Your call.&#xA;&#xA;---&#xA;&#xA;Step 4: Add It to Your Dashboard&#xA;&#xA;A basic card for last-check results:&#xA;&#xA;type: vertical-stack&#xA;cards:&#xA;  type: picture-entity&#xA;    entity: camera.growtent&#xA;    name: Grow Tent&#xA;    showstate: false&#xA;&#xA;  type: entities&#xA;    title: Plant Health&#xA;    entities:&#xA;      entity: sensor.plantlabhealth&#xA;        name: Status&#xA;      entity: sensor.plantlabconditions&#xA;        name: Condition&#xA;      entity: sensor.plantlabpests&#xA;        name: Pests&#xA;      entity: sensor.plantlabgrowthstage&#xA;        name: Growth Stage&#xA;      entity: sensor.plantlabreliabilityscore&#xA;        name: Reliability&#xA;      entity: binarysensor.plantlabproblem&#xA;        name: Problem?&#xA;&#xA;Nothing fancy, but it&#39;s one screen. Make it prettier if you like - I&#39;m an engineer, not a designer.&#xA;&#xA;---&#xA;&#xA;Step 5: The Fun Part&#xA;&#xA;Gate on confidence and reliability&#xA;&#xA;Every diagnosis carries a per-condition confidence and a plant-level reliabilityscore - a 0-1 trust signal for the whole call. You don&#39;t want a push for every marginal detection. Extend the if from Step 3:&#xA;&#xA;      if:   {{ result.results | count   0&#xA;             and result.results[0].ishealthy == false&#xA;             and result.results[0].conditions[0].confidence   0.75&#xA;             and result.results[0].reliabilityscore   0.7 }}&#xA;        then:&#xA;          action: notify.mobileappyourphone&#xA;            data:&#xA;              title: &#34;Confirmed Issue&#34;&#xA;              message:   {{ result.results[0].conditions[0].displayname }}&#xA;                at {{ (result.results[0].conditions[0].confidence  100) | round }}% confidence.&#xA;&#xA;Auto-respond to a specific condition&#xA;&#xA;With smart plugs, dosing pumps, or controllable fans you can close the loop. Read the classid straight off the response - it&#39;s the stable snakecase identifier, unlike the display-name sensor state:&#xA;&#xA;      # inside the same daily-check automation, after the diagnose call&#xA;      if:   {{ result.results | count   0&#xA;             and result.results[0].conditions[0].classid == &#39;calciumdeficiency&#39;&#xA;             and result.results[0].conditions[0].confidence   0.8 }}&#xA;        then:&#xA;          # Dose cal-mag for 5 seconds&#xA;          action: switch.turnon&#xA;            target:&#xA;              entityid: switch.calmagpump&#xA;          delay:&#xA;              seconds: 5&#xA;          action: switch.turnoff&#xA;            target:&#xA;              entityid: switch.calmagpump&#xA;&#xA;          # Always notify when auto-dosing&#xA;          action: notify.mobileappyourphone&#xA;            data:&#xA;              title: &#34;Auto-Dosed Cal-Mag&#34;&#xA;              message:   Calcium deficiency at&#xA;                {{ (result.results[0].conditions[0].confidence  100) | round }}%.&#xA;                Dosed 5 seconds of cal-mag. Check your plant.&#xA;&#xA;Fair warning: always notify when auto-dosing, and set a real confidence floor. Let the system correctly ID a condition manually a few times before you trust it to dose. A pump firing on a false positive is not a fun morning.&#xA;&#xA;Handle more than one plant&#xA;&#xA;If your camera sees the whole tent, results has an entry per plant and sensor.plantlabplantcount tells you how many. Loop instead of assuming results[0]:&#xA;&#xA;      repeat:&#xA;          foreach: &#34;{{ result.results }}&#34;&#xA;          sequence:&#xA;            if: &#34;{{ repeat.item.ishealthy == false }}&#34;&#xA;              then:&#xA;                action: notify.mobileappyourphone&#xA;                  data:&#xA;                    title: &#34;Plant Issue Detected&#34;&#xA;                    message:   {{ repeat.item.conditions[0].displayname }}&#xA;                      ({{ (repeat.item.conditions[0].confidence  100) | round }}%).&#xA;&#xA;The per-plant sensors always track the first plant, so the loop is how you cover a multi-plant frame. If you&#39;d rather diagnose each plant cleanly, give each its own camera and its own automation.&#xA;&#xA;Ramp up monitoring when something&#39;s wrong&#xA;&#xA;automation:&#xA;  alias: &#34;Increase Checks When Unhealthy&#34;&#xA;    trigger:&#xA;      platform: state&#xA;        entityid: binarysensor.plantlabproblem&#xA;        to: &#34;on&#34;&#xA;    action:&#xA;      action: automation.turnon&#xA;        target:&#xA;          entityid: automation.planthealthcheckafternoon&#xA;&#xA;Create a second check (afternoon, maybe a different angle) that&#39;s normally disabled and only wakes up when a problem is flagged. More eyes when it matters, silence otherwise.&#xA;&#xA;---&#xA;&#xA;Troubleshooting&#xA;&#xA;| Problem | Likely cause | Fix |&#xA;|---------|-------------|-----|&#xA;| iscannabis: false, plant count 0 | Camera angle, blurple lights, or a lens cap | Adjust position, use white light or flash, check the camera feed |&#xA;| No notification | Template not matching the new results[] shape | Test in Developer Tools   Template with a real result first |&#xA;| 401 Unauthorized | Invalid API key | Re-enter in Settings   Devices &amp; Services   PlantLab   Configure |&#xA;| Sensors stuck &#34;Unknown&#34; | No diagnosis run yet | Call plantlab.diagnose manually in Developer Tools   Actions |&#xA;| Rate limit (429) | More than 3 checks/day on free tier | Space out automations or upgrade to Pro |&#xA;&#xA;---&#xA;&#xA;What the API Actually Returns&#xA;&#xA;Here&#39;s the full response - everything inside result in your automations. Note the top-level iscannabis (image-wide) versus the per-plant fields nested in results:&#xA;&#xA;PlantLab API response in Home Assistant showing nitrogen deficiency with Mulder&#39;s hypotheses&#xA;&#xA;schemaversion: &#34;3.0.0&#34;&#xA;success: true&#xA;iscannabis: true&#xA;cannabisconfidence: 0.97&#xA;results:&#xA;  bbox: { x0: 0, y0: 0, x1: 1, y1: 1, normalized: true }&#xA;    ishealthy: false&#xA;    healthconfidence: 0.87&#xA;    growthstage: vegetative&#xA;    growthstageconfidence: 0.89&#xA;    conditions:&#xA;      classid: nitrogendeficiency&#xA;        displayname: Nitrogen Deficiency&#xA;        confidence: 0.85&#xA;    pests:&#xA;      classid: spidermites&#xA;        displayname: Spider Mites&#xA;        confidence: 0.72&#xA;    reliabilityscore: 0.82&#xA;    muldershypotheses:&#xA;      excess: potassiumexcess&#xA;        explains:&#xA;          nitrogendeficiency&#xA;        evidence: 0.85&#xA;        evidencecount: 1&#xA;&#xA;reliabilityscore is a 0-1 trust signal for that plant&#39;s diagnosis - higher means PlantLab is more confident the call is right. Route on it for automations that should only fire on high-trust results.&#xA;&#xA;muldershypotheses is the nutrient antagonism read. Here it&#39;s flagging that the nitrogen-deficiency symptoms might trace back to a potassium excess locking out nitrogen uptake, rather than an actual shortage - so piling on more nitrogen could make it worse. That&#39;s the kind of thing that saves a week of chasing the wrong fix.&#xA;&#xA;Each plant in a multi-plant frame gets its own entry with its own bbox (normalized [0,1] canopy box), so you always know which plant a diagnosis belongs to.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;How many checks per day on the free tier?&#xA;&#xA;Three. One morning, one evening, one spare for when you&#39;re feeling paranoid - enough for a home grow. Pro is 500/month if you need more.&#xA;&#xA;Can I use any camera?&#xA;&#xA;If HA can snapshot from it, PlantLab can diagnose from it. Tested with Frigate, Wyze RTSP, ESP32-CAM, Reolink, and Tapo. Phone photos work too - drop the image in /config/www/ and point the service at the path.&#xA;&#xA;Does this work for tomatoes?&#xA;&#xA;No. PlantLab will look at your tomato and politely tell you it&#39;s not cannabis.&#xA;&#xA;PlantLab returning is_cannabis false with an empty results list for a non-cannabis plant&#xA;&#xA;It says healthy but I can see a problem. Now what?&#xA;&#xA;Trust your eyes. The AI catches things you haven&#39;t noticed yet - it&#39;s not there to override what you already see. Early symptoms, odd lighting, and blurry photos all cut accuracy. Cannabis detection and the overall health check are the most reliable parts; specific condition labels, especially lookalike nutrient deficiencies, are harder and keep improving with each retrain.&#xA;&#xA;Can I run this offline?&#xA;&#xA;Not yet - cloud only. On-premise is on the roadmap for air-gapped facilities.&#xA;&#xA;---&#xA;&#xA;PlantLab diagnoses 31 cannabis conditions - nutrient deficiencies, pests, diseases, and environmental stress - from a single photo. The Home Assistant integration is open source at github.com/plantlab-ai/home-assistant-plantlab. Try it free at plantlab.ai.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="what-you-ll-build" id="what-you-ll-build">What You&#39;ll Build</h2>

<p>A camera watches your plants on a schedule. Each photo goes to an AI trained only on cannabis, and Home Assistant turns the answer into sensors you can automate against. Something looks off, your phone buzzes with the diagnosis and the photo. Everything&#39;s fine, it stays quiet.</p>

<p>Setup runs about 20 minutes. The cost is a camera you probably already own plus PlantLab&#39;s free tier – three diagnoses a day, no card. No soldering, no standalone Python scripts. A Home Assistant integration and some YAML.</p>

<p><img src="https://i.snap.as/SVCnmgsR.png" alt="A healthy flowering cannabis plant next to a Home Assistant dashboard card showing PlantLab health, growth stage, and reliability sensors"/></p>



<p>Your eyes are good. They&#39;re also asleep at 6 AM, and they&#39;ve never once compared today&#39;s leaf color against yesterday&#39;s with any real consistency. A camera bolted to a tent pole doesn&#39;t get distracted and has never talked itself into “it&#39;s probably fine” at 11 PM. That&#39;s the whole pitch.</p>

<p>If you run Node-RED instead of native HA automations, there&#39;s a <a href="https://blog.plantlab.ai/node-red-plantlab-automation">companion walkthrough for that</a>. This one stays inside Home Assistant.</p>

<hr/>

<h2 id="prerequisites" id="prerequisites">Prerequisites</h2>
<ul><li><strong>Home Assistant</strong> running – any install method</li>
<li><strong>A camera</strong> HA can snapshot: IP cam, Frigate, ESP32-CAM, Wyze with RTSP, Reolink, Tapo, anything with a <code>camera.</code> entity</li>
<li><strong>A PlantLab account</strong> – free at <a href="https://plantlab.ai">plantlab.ai</a>; grab your API key from the dashboard</li>
<li><strong>HACS</strong> – optional, but it makes the install one click</li>
<li>Basic YAML comfort. If you&#39;ve edited <code>configuration.yaml</code>, you&#39;re set.</li></ul>

<p><strong>Camera positioning:</strong> shoot the canopy from above or a slight angle. Overhead gives the best coverage. Avoid shooting through blurple LEDs – the model needs real leaf color, not everything tinted purple. If your lights are blurple, run the check during a lights-off window or use the camera&#39;s white LED.</p>

<hr/>

<h2 id="step-1-install-the-plantlab-integration" id="step-1-install-the-plantlab-integration">Step 1: Install the PlantLab Integration</h2>

<h3 id="via-hacs-recommended" id="via-hacs-recommended">Via HACS (recommended)</h3>
<ol><li>Open HACS in the sidebar</li>
<li>Search <strong>PlantLab</strong> in Integrations</li>
<li>Click <strong>Download</strong></li>
<li>Restart Home Assistant</li></ol>

<h3 id="manual-install" id="manual-install">Manual install</h3>

<pre><code class="language-bash"># SSH into your HA machine
cd /config/custom_components/
git clone https://github.com/plantlab-ai/home-assistant-plantlab.git plantlab_temp
mv plantlab_temp/custom_components/plantlab .
rm -rf plantlab_temp
</code></pre>

<p>Restart HA after copying.</p>

<h3 id="configure" id="configure">Configure</h3>
<ol><li><strong>Settings &gt; Devices &amp; Services</strong></li>
<li><strong>+ Add Integration</strong></li>
<li>Search <strong>PlantLab</strong></li>
<li>Paste your API key</li>
<li>Done – you get a PlantLab device with a set of sensors.</li></ol>

<p>No YAML for the integration itself. The config flow handles it.</p>

<hr/>

<h2 id="step-2-meet-your-new-sensors" id="step-2-meet-your-new-sensors">Step 2: Meet Your New Sensors</h2>

<p>After setup you get these entities automatically:</p>

<table>
<thead>
<tr>
<th>Entity</th>
<th>What it shows</th>
<th>Example state</th>
</tr>
</thead>

<tbody>
<tr>
<td><code>sensor.plantlab_health</code></td>
<td>Overall verdict</td>
<td><code>unhealthy</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_conditions</code></td>
<td>Top condition</td>
<td><code>Nitrogen Deficiency</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_pests</code></td>
<td>Top pest</td>
<td><code>Spider Mites</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_growth_stage</code></td>
<td>Current stage</td>
<td><code>vegetative</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_nutrient_analysis</code></td>
<td>Mulder&#39;s Chart hypothesis</td>
<td><code>potassium_excess</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_reliability_score</code></td>
<td>How much to trust this call (0-100%)</td>
<td><code>82.0</code></td>
</tr>

<tr>
<td><code>sensor.plantlab_plant_count</code></td>
<td>Plants detected in the frame</td>
<td><code>1</code></td>
</tr>

<tr>
<td><code>binary_sensor.plantlab_problem</code></td>
<td>Simple on/off</td>
<td><code>on</code> when something&#39;s wrong</td>
</tr>
</tbody>
</table>

<p>One thing worth flagging up front: the condition and pest sensors report the <strong>display name</strong> – <code>Nitrogen Deficiency</code>, not <code>nitrogen_deficiency</code>. That matters when you trigger automations on their state. The raw snake_case <code>class_id</code> lives in the sensor&#39;s attributes and in the service response, which is what you&#39;ll actually key automations off of below.</p>

<p>Everything reads “Unknown” until the first diagnosis runs. After a check:</p>

<p><img src="https://i.snap.as/M2uEy0pz.png" alt="PlantLab sensor states in Home Assistant showing nitrogen deficiency detected"/></p>

<hr/>

<h2 id="step-3-the-daily-health-check" id="step-3-the-daily-health-check">Step 3: The Daily Health Check</h2>

<p>Runs once a day, grabs a photo, sends it to PlantLab, and pings you only if there&#39;s a problem. Quiet when fine, loud when not.</p>

<pre><code class="language-yaml">automation:
  - alias: &#34;Plant Health Check - Morning&#34;
    trigger:
      - platform: time
        at: &#34;08:00:00&#34;
    action:
      # Capture a snapshot
      - action: camera.snapshot
        target:
          entity_id: camera.grow_tent    # your camera entity
        data:
          filename: /config/www/plant_check.jpg

      # Give the file a moment to write
      - delay:
          seconds: 3

      # Run the diagnosis
      - action: plantlab.diagnose
        data:
          image_path: /config/www/plant_check.jpg
        response_variable: result

      # Alert only if the first plant comes back unhealthy
      - if: &gt;
          {{ result.results | count &gt; 0
             and result.results[0].is_healthy == false }}
        then:
          - action: notify.mobile_app_your_phone
            data:
              title: &#34;Plant Issue Detected&#34;
              message: &gt;
                {{ result.results[0].conditions[0].display_name }}
                detected ({{ (result.results[0].conditions[0].confidence * 100) | round }}% confidence).
                Growth stage: {{ result.results[0].growth_stage }}.
              data:
                image: /local/plant_check.jpg
</code></pre>

<p>One structural thing to know: PlantLab returns <strong>one result per detected plant</strong> under <code>results</code>. <code>result.results[0]</code> is the first plant, which is why the health check, the condition, and the confidence all read off <code>results[0]</code> and not the top level. More on multi-plant in a moment.</p>

<p><strong>Why morning?</strong> You catch overnight problems before the lights-on period drives symptoms further, and the first hour of the light cycle gives a neutral photo without heat-droop.</p>

<h3 id="skip-the-file-snapshot-from-the-camera-directly" id="skip-the-file-snapshot-from-the-camera-directly">Skip the file – snapshot from the camera directly</h3>

<p>Don&#39;t want files on disk? Point the service at the camera entity:</p>

<pre><code class="language-yaml">      - action: plantlab.diagnose
        data:
          entity_id: camera.grow_tent
        response_variable: result
</code></pre>

<p>Simpler, but you lose the photo to attach to the notification. Your call.</p>

<hr/>

<h2 id="step-4-add-it-to-your-dashboard" id="step-4-add-it-to-your-dashboard">Step 4: Add It to Your Dashboard</h2>

<p>A basic card for last-check results:</p>

<pre><code class="language-yaml">type: vertical-stack
cards:
  - type: picture-entity
    entity: camera.grow_tent
    name: Grow Tent
    show_state: false

  - type: entities
    title: Plant Health
    entities:
      - entity: sensor.plantlab_health
        name: Status
      - entity: sensor.plantlab_conditions
        name: Condition
      - entity: sensor.plantlab_pests
        name: Pests
      - entity: sensor.plantlab_growth_stage
        name: Growth Stage
      - entity: sensor.plantlab_reliability_score
        name: Reliability
      - entity: binary_sensor.plantlab_problem
        name: Problem?
</code></pre>

<p>Nothing fancy, but it&#39;s one screen. Make it prettier if you like – I&#39;m an engineer, not a designer.</p>

<hr/>

<h2 id="step-5-the-fun-part" id="step-5-the-fun-part">Step 5: The Fun Part</h2>

<h3 id="gate-on-confidence-and-reliability" id="gate-on-confidence-and-reliability">Gate on confidence and reliability</h3>

<p>Every diagnosis carries a per-condition confidence and a plant-level <code>reliability_score</code> – a 0-1 trust signal for the whole call. You don&#39;t want a push for every marginal detection. Extend the <code>if</code> from Step 3:</p>

<pre><code class="language-yaml">      - if: &gt;
          {{ result.results | count &gt; 0
             and result.results[0].is_healthy == false
             and result.results[0].conditions[0].confidence &gt; 0.75
             and result.results[0].reliability_score &gt; 0.7 }}
        then:
          - action: notify.mobile_app_your_phone
            data:
              title: &#34;Confirmed Issue&#34;
              message: &gt;
                {{ result.results[0].conditions[0].display_name }}
                at {{ (result.results[0].conditions[0].confidence * 100) | round }}% confidence.
</code></pre>

<h3 id="auto-respond-to-a-specific-condition" id="auto-respond-to-a-specific-condition">Auto-respond to a specific condition</h3>

<p>With smart plugs, dosing pumps, or controllable fans you can close the loop. Read the <code>class_id</code> straight off the response – it&#39;s the stable snake_case identifier, unlike the display-name sensor state:</p>

<pre><code class="language-yaml">      # inside the same daily-check automation, after the diagnose call
      - if: &gt;
          {{ result.results | count &gt; 0
             and result.results[0].conditions[0].class_id == &#39;calcium_deficiency&#39;
             and result.results[0].conditions[0].confidence &gt; 0.8 }}
        then:
          # Dose cal-mag for 5 seconds
          - action: switch.turn_on
            target:
              entity_id: switch.calmag_pump
          - delay:
              seconds: 5
          - action: switch.turn_off
            target:
              entity_id: switch.calmag_pump

          # Always notify when auto-dosing
          - action: notify.mobile_app_your_phone
            data:
              title: &#34;Auto-Dosed Cal-Mag&#34;
              message: &gt;
                Calcium deficiency at
                {{ (result.results[0].conditions[0].confidence * 100) | round }}%.
                Dosed 5 seconds of cal-mag. Check your plant.
</code></pre>

<p>Fair warning: always notify when auto-dosing, and set a real confidence floor. Let the system correctly ID a condition manually a few times before you trust it to dose. A pump firing on a false positive is not a fun morning.</p>

<h3 id="handle-more-than-one-plant" id="handle-more-than-one-plant">Handle more than one plant</h3>

<p>If your camera sees the whole tent, <code>results</code> has an entry per plant and <code>sensor.plantlab_plant_count</code> tells you how many. Loop instead of assuming <code>results[0]</code>:</p>

<pre><code class="language-yaml">      - repeat:
          for_each: &#34;{{ result.results }}&#34;
          sequence:
            - if: &#34;{{ repeat.item.is_healthy == false }}&#34;
              then:
                - action: notify.mobile_app_your_phone
                  data:
                    title: &#34;Plant Issue Detected&#34;
                    message: &gt;
                      {{ repeat.item.conditions[0].display_name }}
                      ({{ (repeat.item.conditions[0].confidence * 100) | round }}%).
</code></pre>

<p>The per-plant sensors always track the first plant, so the loop is how you cover a multi-plant frame. If you&#39;d rather diagnose each plant cleanly, give each its own camera and its own automation.</p>

<h3 id="ramp-up-monitoring-when-something-s-wrong" id="ramp-up-monitoring-when-something-s-wrong">Ramp up monitoring when something&#39;s wrong</h3>

<pre><code class="language-yaml">automation:
  - alias: &#34;Increase Checks When Unhealthy&#34;
    trigger:
      - platform: state
        entity_id: binary_sensor.plantlab_problem
        to: &#34;on&#34;
    action:
      - action: automation.turn_on
        target:
          entity_id: automation.plant_health_check_afternoon
</code></pre>

<p>Create a second check (afternoon, maybe a different angle) that&#39;s normally disabled and only wakes up when a problem is flagged. More eyes when it matters, silence otherwise.</p>

<hr/>

<h2 id="troubleshooting" id="troubleshooting">Troubleshooting</h2>

<table>
<thead>
<tr>
<th>Problem</th>
<th>Likely cause</th>
<th>Fix</th>
</tr>
</thead>

<tbody>
<tr>
<td><code>is_cannabis: false</code>, plant count 0</td>
<td>Camera angle, blurple lights, or a lens cap</td>
<td>Adjust position, use white light or flash, check the camera feed</td>
</tr>

<tr>
<td>No notification</td>
<td>Template not matching the new <code>results[]</code> shape</td>
<td>Test in Developer Tools &gt; Template with a real <code>result</code> first</td>
</tr>

<tr>
<td>401 Unauthorized</td>
<td>Invalid API key</td>
<td>Re-enter in Settings &gt; Devices &amp; Services &gt; PlantLab &gt; Configure</td>
</tr>

<tr>
<td>Sensors stuck “Unknown”</td>
<td>No diagnosis run yet</td>
<td>Call <code>plantlab.diagnose</code> manually in Developer Tools &gt; Actions</td>
</tr>

<tr>
<td>Rate limit (429)</td>
<td>More than 3 checks/day on free tier</td>
<td>Space out automations or upgrade to Pro</td>
</tr>
</tbody>
</table>

<hr/>

<h2 id="what-the-api-actually-returns" id="what-the-api-actually-returns">What the API Actually Returns</h2>

<p>Here&#39;s the full response – everything inside <code>result</code> in your automations. Note the top-level <code>is_cannabis</code> (image-wide) versus the per-plant fields nested in <code>results</code>:</p>

<p><img src="https://i.snap.as/U3QV3DW2.png" alt="PlantLab API response in Home Assistant showing nitrogen deficiency with Mulder&#39;s hypotheses"/></p>

<pre><code class="language-yaml">schema_version: &#34;3.0.0&#34;
success: true
is_cannabis: true
cannabis_confidence: 0.97
results:
  - bbox: { x0: 0, y0: 0, x1: 1, y1: 1, normalized: true }
    is_healthy: false
    health_confidence: 0.87
    growth_stage: vegetative
    growth_stage_confidence: 0.89
    conditions:
      - class_id: nitrogen_deficiency
        display_name: Nitrogen Deficiency
        confidence: 0.85
    pests:
      - class_id: spider_mites
        display_name: Spider Mites
        confidence: 0.72
    reliability_score: 0.82
    mulders_hypotheses:
      - excess: potassium_excess
        explains:
          - nitrogen_deficiency
        evidence: 0.85
        evidence_count: 1
</code></pre>

<p><code>reliability_score</code> is a 0-1 trust signal for that plant&#39;s diagnosis – higher means PlantLab is more confident the call is right. Route on it for automations that should only fire on high-trust results.</p>

<p><code>mulders_hypotheses</code> is the nutrient antagonism read. Here it&#39;s flagging that the nitrogen-deficiency symptoms might trace back to a potassium excess locking out nitrogen uptake, rather than an actual shortage – so piling on more nitrogen could make it worse. That&#39;s the kind of thing that saves a week of chasing the wrong fix.</p>

<p>Each plant in a multi-plant frame gets its own entry with its own <code>bbox</code> (normalized <code>[0,1]</code> canopy box), so you always know which plant a diagnosis belongs to.</p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>How many checks per day on the free tier?</strong></p>

<p>Three. One morning, one evening, one spare for when you&#39;re feeling paranoid – enough for a home grow. Pro is 500/month if you need more.</p>

<p><strong>Can I use any camera?</strong></p>

<p>If HA can snapshot from it, PlantLab can diagnose from it. Tested with Frigate, Wyze RTSP, ESP32-CAM, Reolink, and Tapo. Phone photos work too – drop the image in <code>/config/www/</code> and point the service at the path.</p>

<p><strong>Does this work for tomatoes?</strong></p>

<p>No. PlantLab will look at your tomato and politely tell you it&#39;s not cannabis.</p>

<p><img src="https://i.snap.as/BR5ovtUB.png" alt="PlantLab returning is_cannabis false with an empty results list for a non-cannabis plant"/></p>

<p><strong>It says healthy but I can see a problem. Now what?</strong></p>

<p>Trust your eyes. The AI catches things you haven&#39;t noticed yet – it&#39;s not there to override what you already see. Early symptoms, odd lighting, and blurry photos all cut accuracy. Cannabis detection and the overall health check are the most reliable parts; specific condition labels, especially lookalike nutrient deficiencies, are harder and keep improving with each retrain.</p>

<p><strong>Can I run this offline?</strong></p>

<p>Not yet – cloud only. On-premise is on the roadmap for air-gapped facilities.</p>

<hr/>

<p><em>PlantLab diagnoses 31 cannabis conditions – nutrient deficiencies, pests, diseases, and environmental stress – from a single photo. The Home Assistant integration is open source at <a href="https://github.com/plantlab-ai/home-assistant-plantlab">github.com/plantlab-ai/home-assistant-plantlab</a>. Try it free at <a href="https://plantlab.ai">plantlab.ai</a>.</em></p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/home-assistant-plant-monitoring</guid>
      <pubDate>Mon, 13 Jul 2026 21:24:52 +0000</pubDate>
    </item>
    <item>
      <title>PlantLab Now Diagnoses Multiple Plants in One Photo</title>
      <link>https://blog.plantlab.ai/plantlab-multi-plant-analysis-schema-3?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[The short version&#xA;&#xA;PlantLab can now analyze more than one plant in a single uploaded photo. Instead of forcing the whole image into one diagnosis, the API slices separable plants into their own canopy boxes, runs the diagnosis cascade per plant, and returns a results[] array with one entry per plant.&#xA;&#xA;This is a breaking API change. The response schema is now 3.0.0. Fields like ishealthy, growthstage, conditions, pests, and reliabilityscore moved out of the top level and into results[]. Image-level fields such as iscannabis and cannabisconfidence stay top-level.&#xA;&#xA;If your code already treats a diagnosis as &#34;the answer for this plant,&#34; the migration is simple: iterate results[]. Single-plant photos still return exactly one result.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;Why this had to change&#xA;&#xA;Most plant diagnosis tools assume one photo equals one plant.&#xA;&#xA;That is convenient for an API contract. It is not how people take grow-room photos.&#xA;&#xA;Growers send canopy shots. They send side-by-side plants from the same tray. They send one wide image because it is faster than taking six separate photos. Sometimes one plant is healthy and the plant beside it is showing early deficiency. Sometimes the left side of a tent is getting different airflow or light intensity than the right side.&#xA;&#xA;The old PlantLab response could only represent one diagnosis. If the image contained three plants, the model still had to answer as if it were looking at one object. That creates two bad outcomes.&#xA;&#xA;First, the answer can become a blend. A healthy plant and a deficient plant in the same frame can collapse into a single diagnosis that is not quite true for either plant.&#xA;&#xA;Second, the UI has no place to show location. Even when the model found the right problem, it could not say &#34;this plant, in this part of the image.&#34; For automation and history, that is a real limitation. A diagnosis without a region is hard to compare over time.&#xA;&#xA;The fix was not another confidence field. It was a different shape of response.&#xA;&#xA;---&#xA;&#xA;What changes for growers&#xA;&#xA;When the photo contains one plant, the experience should feel the same. PlantLab returns one diagnosis, with a full-image bounding box:&#xA;&#xA;&#34;results&#34;: [&#xA;  {&#xA;    &#34;bbox&#34;: { &#34;x0&#34;: 0, &#34;y0&#34;: 0, &#34;x1&#34;: 1, &#34;y1&#34;: 1, &#34;normalized&#34;: true },&#xA;    &#34;ishealthy&#34;: false,&#xA;    &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;    &#34;conditions&#34;: [&#xA;      { &#34;classid&#34;: &#34;magnesiumdeficiency&#34;, &#34;confidence&#34;: 0.85 }&#xA;    ],&#xA;    &#34;reliabilityscore&#34;: 0.87&#xA;  }&#xA;]&#xA;&#xA;When the photo contains multiple separable plants, PlantLab returns multiple entries. Each entry has its own normalized bounding box and its own diagnosis fields:&#xA;&#xA;{&#xA;  &#34;schemaversion&#34;: &#34;3.0.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;cannabisconfidence&#34;: 0.99,&#xA;  &#34;results&#34;: [&#xA;    {&#xA;      &#34;bbox&#34;: { &#34;x0&#34;: 0.06, &#34;y0&#34;: 0.12, &#34;x1&#34;: 0.45, &#34;y1&#34;: 0.92, &#34;normalized&#34;: true },&#xA;      &#34;ishealthy&#34;: true,&#xA;      &#34;healthconfidence&#34;: 0.91,&#xA;      &#34;growthstage&#34;: &#34;vegetative&#34;&#xA;    },&#xA;    {&#xA;      &#34;bbox&#34;: { &#34;x0&#34;: 0.52, &#34;y0&#34;: 0.10, &#34;x1&#34;: 0.93, &#34;y1&#34;: 0.95, &#34;normalized&#34;: true },&#xA;      &#34;ishealthy&#34;: false,&#xA;      &#34;healthconfidence&#34;: 0.88,&#xA;      &#34;growthstage&#34;: &#34;vegetative&#34;,&#xA;      &#34;conditions&#34;: [&#xA;        { &#34;classid&#34;: &#34;nitrogendeficiency&#34;, &#34;confidence&#34;: 0.80 }&#xA;      ],&#xA;      &#34;reliabilityscore&#34;: 0.83&#xA;    }&#xA;  ]&#xA;}&#xA;&#xA;The boxes are normalized x0, y0, x1, y1 coordinates in the original image. They are designed for overlays, history views, and automation clients that need to keep a result tied to the plant it came from.&#xA;&#xA;The original uploaded image stays the canonical image. PlantLab does not store a separate cropped image for each plant as the primary record. The boxes are metadata attached to the original frame.&#xA;&#xA;---&#xA;&#xA;Why the response is an array, not plant1, plant2, plant3&#xA;&#xA;Arrays are boring. That is why they are the right answer.&#xA;&#xA;A grow tent can have one plant today and four plants next week. A user can upload a single close-up, then a wide tray shot, then a photo where the plants overlap too much to split safely. The API should not need new field names for each case.&#xA;&#xA;With results[], the contract is stable:&#xA;&#xA;len(results) == 1: use it like the old response.&#xA;len(results)   1: show a plant selector or iterate through every result.&#xA;Each result carries its own bbox.&#xA;&#xA;This also makes the API easier for automation systems. If you are feeding PlantLab into Home Assistant, Node-RED, a dashboard, or a cultivation controller, each plant result is a normal object. You can pick the first plant for backward-compatible behavior, show a plant count, or build a UI that lets the user choose which plant they care about.&#xA;&#xA;The PlantLab Home Assistant integration has already been updated for this shape. Version 0.7.0 reads schema 3.0.0, keeps the existing sensors pointed at the primary plant (results[0]), and adds sensor.plantlabplantcount so automations can tell when the last frame held more than one plant.&#xA;&#xA;---&#xA;&#xA;What changed for API consumers&#xA;&#xA;Before schema 3.0.0, diagnosis fields were top-level:&#xA;&#xA;{&#xA;  &#34;schemaversion&#34;: &#34;2.1.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;cannabisconfidence&#34;: 0.99,&#xA;  &#34;ishealthy&#34;: false,&#xA;  &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;  &#34;conditions&#34;: [&#xA;    { &#34;classid&#34;: &#34;magnesiumdeficiency&#34;, &#34;confidence&#34;: 0.85 }&#xA;  ],&#xA;  &#34;reliabilityscore&#34;: 0.91&#xA;}&#xA;&#xA;In schema 3.0.0, those diagnosis fields live inside results[]:&#xA;&#xA;{&#xA;  &#34;schemaversion&#34;: &#34;3.0.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;cannabisconfidence&#34;: 0.99,&#xA;  &#34;results&#34;: [&#xA;    {&#xA;      &#34;bbox&#34;: { &#34;x0&#34;: 0, &#34;y0&#34;: 0, &#34;x1&#34;: 1, &#34;y1&#34;: 1, &#34;normalized&#34;: true },&#xA;      &#34;ishealthy&#34;: false,&#xA;      &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;      &#34;conditions&#34;: [&#xA;        { &#34;classid&#34;: &#34;magnesiumdeficiency&#34;, &#34;confidence&#34;: 0.85 }&#xA;      ],&#xA;      &#34;reliabilityscore&#34;: 0.91&#xA;    }&#xA;  ]&#xA;}&#xA;&#xA;Migration pattern:&#xA;&#xA;const primaryPlant = response.results?.[0]&#xA;&#xA;if (primaryPlant?.ishealthy === false) {&#xA;  for (const condition of primaryPlant.conditions ?? []) {&#xA;    console.log(condition.classid, condition.confidence)&#xA;  }&#xA;}&#xA;&#xA;If your integration displays only one diagnosis, start with results[0]. That gives you a safe primary-plant path while you add richer multi-plant UI later.&#xA;&#xA;If your integration can display multiple plants, iterate the array and draw each bbox over the original image.&#xA;&#xA;If you use the official Home Assistant integration, update to v0.7.0. It is rollout-friendly: the updated integration understands the new results[] response, but it also falls back to the old flat fields when talking to a pre-3.0.0 API. That means you can update Home Assistant before the API flips without breaking existing sensors. Older integration versions should be upgraded before you depend on schema 3.0.0.&#xA;&#xA;---&#xA;&#xA;Why I made it breaking&#xA;&#xA;I considered keeping the old top-level fields for one release and adding results[] beside them. That sounds friendlier until the two disagree.&#xA;&#xA;Imagine an image with two plants:&#xA;&#xA;Plant A is healthy.&#xA;Plant B has a deficiency.&#xA;&#xA;What should the old top-level ishealthy say? If it says false, the healthy plant is wrong. If it says true, the deficient plant is wrong. If it tries to summarize the whole image, it stops being the same field that integrators already rely on.&#xA;&#xA;Keeping both contracts would make the API easier to call and harder to trust. I would rather force one clear migration than leave stale fields around for months.&#xA;&#xA;So the schema version bumped to 3.0.0. Consumers must read results[].&#xA;&#xA;---&#xA;&#xA;What PlantLab does when the image is messy&#xA;&#xA;Multi-plant analysis is only useful when the plants can be separated cleanly enough to diagnose.&#xA;&#xA;Dense canopy shots are hard. Touching plants, heavy overlap, blur, and poor lighting can make a crop ambiguous. Splitting too aggressively is worse than under-splitting, because an over-split can create contradictory diagnoses from pieces of the same plant.&#xA;&#xA;PlantLab uses a conservative policy:&#xA;&#xA;If the image looks like one plant, return one result.&#xA;If the plants are separable, return one result per plant.&#xA;If the scene is too dense or ambiguous, prefer one safer result over several questionable crops.&#xA;Cap the number of plant crops so latency stays bounded.&#xA;&#xA;That last part matters. A multi-plant image now runs a lightweight slicing step, then the diagnosis cascade per plant. We also removed a wasted whole-image cascade for multi-plant paths, so a three-plant image runs the plant diagnosis work three times, not four.&#xA;&#xA;The point is not to pretend every canopy photo is solvable. The point is to make the output honest about the structure of the image.&#xA;&#xA;---&#xA;&#xA;What this unlocks&#xA;&#xA;For growers, this makes wide shots more useful. You can upload a photo of a tray and see which plant the diagnosis belongs to.&#xA;&#xA;For paid history, bounding boxes make comparison over time more meaningful. A diagnosis can be stored with the region it came from instead of being attached only to the original image.&#xA;&#xA;For automation, the response is finally shaped like the thing it describes. A controller can loop over plants, display per-plant state, or decide to alert only when any plant crosses a threshold.&#xA;&#xA;For training, this closes a long-standing mismatch. A whole-frame label is often too crude for a multi-plant image. Per-plant boxes let the system learn from the plant region without pretending the entire image has one uniform condition.&#xA;&#xA;This is the main reason I was willing to break the schema. The old response was simpler, but it encoded the wrong assumption.&#xA;&#xA;---&#xA;&#xA;Migration checklist&#xA;&#xA;If you maintain a PlantLab client, check these paths:&#xA;&#xA;Replace reads of top-level ishealthy, healthconfidence, growthstage, conditions, pests, muldershypotheses, reasoning fields, and reliabilityscore with reads from results[].&#xA;Keep reading top-level iscannabis and cannabisconfidence.&#xA;Treat results[0] as the primary plant if you need backward-compatible behavior.&#xA;Use len(results) as the plant count.&#xA;Draw result.bbox over the original uploaded image if your UI supports overlays.&#xA;Treat {x0:0, y0:0, x1:1, y1:1} as the whole-image fallback box.&#xA;If you use Home Assistant, update plantlab-ai/home-assistant-plantlab to v0.7.0. Existing diagnosis sensors continue to show the primary plant, and the new sensor.plantlabplantcount exposes len(results).&#xA;&#xA;The full OpenAPI schema is available in the PlantLab docs at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, structured JSON responses, and API docs built for automation clients.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;Does every upload now return multiple plants?&#xA;&#xA;No. Single-plant images return one result. Ambiguous dense canopy images may also return one result if splitting would be unsafe.&#xA;&#xA;Did the old fields disappear?&#xA;&#xA;Yes. Per-plant diagnosis fields moved into results[] in schema 3.0.0. Top-level iscannabis and cannabisconfidence remain image-level fields.&#xA;&#xA;How do I get the plant count?&#xA;&#xA;Use response.results.length.&#xA;&#xA;Are the bounding boxes pixel coordinates?&#xA;&#xA;No. They are normalized coordinates from 0 to 1, relative to the original image. Multiply by image width and height when drawing overlays.&#xA;&#xA;What should older clients do?&#xA;&#xA;Read results[0] first. That restores the old &#34;one diagnosis&#34; behavior while keeping your code compatible with multi-plant uploads.&#xA;&#xA;Is the Home Assistant integration ready?&#xA;&#xA;Yes. The official Home Assistant integration is updated in v0.7.0. It reads schema 3.0.0, surfaces the primary plant through the existing sensors, adds sensor.plantlabplant_count, and still tolerates pre-3.0.0 flat API responses during rollout.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="the-short-version" id="the-short-version">The short version</h2>

<p>PlantLab can now analyze more than one plant in a single uploaded photo. Instead of forcing the whole image into one diagnosis, the API slices separable plants into their own canopy boxes, runs the diagnosis cascade per plant, and returns a <code>results[]</code> array with one entry per plant.</p>

<p>This is a breaking API change. The response schema is now <code>3.0.0</code>. Fields like <code>is_healthy</code>, <code>growth_stage</code>, <code>conditions</code>, <code>pests</code>, and <code>reliability_score</code> moved out of the top level and into <code>results[]</code>. Image-level fields such as <code>is_cannabis</code> and <code>cannabis_confidence</code> stay top-level.</p>

<p>If your code already treats a diagnosis as “the answer for this plant,” the migration is simple: iterate <code>results[]</code>. Single-plant photos still return exactly one result.</p>



<hr/>

<h2 id="why-this-had-to-change" id="why-this-had-to-change">Why this had to change</h2>

<p>Most plant diagnosis tools assume one photo equals one plant.</p>

<p>That is convenient for an API contract. It is not how people take grow-room photos.</p>

<p>Growers send canopy shots. They send side-by-side plants from the same tray. They send one wide image because it is faster than taking six separate photos. Sometimes one plant is healthy and the plant beside it is showing early deficiency. Sometimes the left side of a tent is getting different airflow or light intensity than the right side.</p>

<p>The old PlantLab response could only represent one diagnosis. If the image contained three plants, the model still had to answer as if it were looking at one object. That creates two bad outcomes.</p>

<p>First, the answer can become a blend. A healthy plant and a deficient plant in the same frame can collapse into a single diagnosis that is not quite true for either plant.</p>

<p>Second, the UI has no place to show location. Even when the model found the right problem, it could not say “this plant, in this part of the image.” For automation and history, that is a real limitation. A diagnosis without a region is hard to compare over time.</p>

<p>The fix was not another confidence field. It was a different shape of response.</p>

<hr/>

<h2 id="what-changes-for-growers" id="what-changes-for-growers">What changes for growers</h2>

<p>When the photo contains one plant, the experience should feel the same. PlantLab returns one diagnosis, with a full-image bounding box:</p>

<pre><code class="language-json">&#34;results&#34;: [
  {
    &#34;bbox&#34;: { &#34;x0&#34;: 0, &#34;y0&#34;: 0, &#34;x1&#34;: 1, &#34;y1&#34;: 1, &#34;normalized&#34;: true },
    &#34;is_healthy&#34;: false,
    &#34;growth_stage&#34;: &#34;flowering&#34;,
    &#34;conditions&#34;: [
      { &#34;class_id&#34;: &#34;magnesium_deficiency&#34;, &#34;confidence&#34;: 0.85 }
    ],
    &#34;reliability_score&#34;: 0.87
  }
]
</code></pre>

<p>When the photo contains multiple separable plants, PlantLab returns multiple entries. Each entry has its own normalized bounding box and its own diagnosis fields:</p>

<pre><code class="language-json">{
  &#34;schema_version&#34;: &#34;3.0.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;cannabis_confidence&#34;: 0.99,
  &#34;results&#34;: [
    {
      &#34;bbox&#34;: { &#34;x0&#34;: 0.06, &#34;y0&#34;: 0.12, &#34;x1&#34;: 0.45, &#34;y1&#34;: 0.92, &#34;normalized&#34;: true },
      &#34;is_healthy&#34;: true,
      &#34;health_confidence&#34;: 0.91,
      &#34;growth_stage&#34;: &#34;vegetative&#34;
    },
    {
      &#34;bbox&#34;: { &#34;x0&#34;: 0.52, &#34;y0&#34;: 0.10, &#34;x1&#34;: 0.93, &#34;y1&#34;: 0.95, &#34;normalized&#34;: true },
      &#34;is_healthy&#34;: false,
      &#34;health_confidence&#34;: 0.88,
      &#34;growth_stage&#34;: &#34;vegetative&#34;,
      &#34;conditions&#34;: [
        { &#34;class_id&#34;: &#34;nitrogen_deficiency&#34;, &#34;confidence&#34;: 0.80 }
      ],
      &#34;reliability_score&#34;: 0.83
    }
  ]
}
</code></pre>

<p>The boxes are normalized <code>x0</code>, <code>y0</code>, <code>x1</code>, <code>y1</code> coordinates in the original image. They are designed for overlays, history views, and automation clients that need to keep a result tied to the plant it came from.</p>

<p>The original uploaded image stays the canonical image. PlantLab does not store a separate cropped image for each plant as the primary record. The boxes are metadata attached to the original frame.</p>

<hr/>

<h2 id="why-the-response-is-an-array-not-plant-1-plant-2-plant-3" id="why-the-response-is-an-array-not-plant-1-plant-2-plant-3">Why the response is an array, not <code>plant_1</code>, <code>plant_2</code>, <code>plant_3</code></h2>

<p>Arrays are boring. That is why they are the right answer.</p>

<p>A grow tent can have one plant today and four plants next week. A user can upload a single close-up, then a wide tray shot, then a photo where the plants overlap too much to split safely. The API should not need new field names for each case.</p>

<p>With <code>results[]</code>, the contract is stable:</p>
<ul><li><code>len(results) == 1</code>: use it like the old response.</li>
<li><code>len(results) &gt; 1</code>: show a plant selector or iterate through every result.</li>
<li>Each result carries its own <code>bbox</code>.</li></ul>

<p>This also makes the API easier for automation systems. If you are feeding PlantLab into Home Assistant, Node-RED, a dashboard, or a cultivation controller, each plant result is a normal object. You can pick the first plant for backward-compatible behavior, show a plant count, or build a UI that lets the user choose which plant they care about.</p>

<p>The PlantLab Home Assistant integration has already been updated for this shape. Version <code>0.7.0</code> reads schema <code>3.0.0</code>, keeps the existing sensors pointed at the primary plant (<code>results[0]</code>), and adds <code>sensor.plantlab_plant_count</code> so automations can tell when the last frame held more than one plant.</p>

<hr/>

<h2 id="what-changed-for-api-consumers" id="what-changed-for-api-consumers">What changed for API consumers</h2>

<p>Before schema 3.0.0, diagnosis fields were top-level:</p>

<pre><code class="language-json">{
  &#34;schema_version&#34;: &#34;2.1.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;cannabis_confidence&#34;: 0.99,
  &#34;is_healthy&#34;: false,
  &#34;growth_stage&#34;: &#34;flowering&#34;,
  &#34;conditions&#34;: [
    { &#34;class_id&#34;: &#34;magnesium_deficiency&#34;, &#34;confidence&#34;: 0.85 }
  ],
  &#34;reliability_score&#34;: 0.91
}
</code></pre>

<p>In schema 3.0.0, those diagnosis fields live inside <code>results[]</code>:</p>

<pre><code class="language-json">{
  &#34;schema_version&#34;: &#34;3.0.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;cannabis_confidence&#34;: 0.99,
  &#34;results&#34;: [
    {
      &#34;bbox&#34;: { &#34;x0&#34;: 0, &#34;y0&#34;: 0, &#34;x1&#34;: 1, &#34;y1&#34;: 1, &#34;normalized&#34;: true },
      &#34;is_healthy&#34;: false,
      &#34;growth_stage&#34;: &#34;flowering&#34;,
      &#34;conditions&#34;: [
        { &#34;class_id&#34;: &#34;magnesium_deficiency&#34;, &#34;confidence&#34;: 0.85 }
      ],
      &#34;reliability_score&#34;: 0.91
    }
  ]
}
</code></pre>

<p>Migration pattern:</p>

<pre><code class="language-js">const primaryPlant = response.results?.[0]

if (primaryPlant?.is_healthy === false) {
  for (const condition of primaryPlant.conditions ?? []) {
    console.log(condition.class_id, condition.confidence)
  }
}
</code></pre>

<p>If your integration displays only one diagnosis, start with <code>results[0]</code>. That gives you a safe primary-plant path while you add richer multi-plant UI later.</p>

<p>If your integration can display multiple plants, iterate the array and draw each <code>bbox</code> over the original image.</p>

<p>If you use the official Home Assistant integration, update to <code>v0.7.0</code>. It is rollout-friendly: the updated integration understands the new <code>results[]</code> response, but it also falls back to the old flat fields when talking to a pre-3.0.0 API. That means you can update Home Assistant before the API flips without breaking existing sensors. Older integration versions should be upgraded before you depend on schema <code>3.0.0</code>.</p>

<hr/>

<h2 id="why-i-made-it-breaking" id="why-i-made-it-breaking">Why I made it breaking</h2>

<p>I considered keeping the old top-level fields for one release and adding <code>results[]</code> beside them. That sounds friendlier until the two disagree.</p>

<p>Imagine an image with two plants:</p>
<ul><li>Plant A is healthy.</li>
<li>Plant B has a deficiency.</li></ul>

<p>What should the old top-level <code>is_healthy</code> say? If it says <code>false</code>, the healthy plant is wrong. If it says <code>true</code>, the deficient plant is wrong. If it tries to summarize the whole image, it stops being the same field that integrators already rely on.</p>

<p>Keeping both contracts would make the API easier to call and harder to trust. I would rather force one clear migration than leave stale fields around for months.</p>

<p>So the schema version bumped to <code>3.0.0</code>. Consumers must read <code>results[]</code>.</p>

<hr/>

<h2 id="what-plantlab-does-when-the-image-is-messy" id="what-plantlab-does-when-the-image-is-messy">What PlantLab does when the image is messy</h2>

<p>Multi-plant analysis is only useful when the plants can be separated cleanly enough to diagnose.</p>

<p>Dense canopy shots are hard. Touching plants, heavy overlap, blur, and poor lighting can make a crop ambiguous. Splitting too aggressively is worse than under-splitting, because an over-split can create contradictory diagnoses from pieces of the same plant.</p>

<p>PlantLab uses a conservative policy:</p>
<ul><li>If the image looks like one plant, return one result.</li>
<li>If the plants are separable, return one result per plant.</li>
<li>If the scene is too dense or ambiguous, prefer one safer result over several questionable crops.</li>
<li>Cap the number of plant crops so latency stays bounded.</li></ul>

<p>That last part matters. A multi-plant image now runs a lightweight slicing step, then the diagnosis cascade per plant. We also removed a wasted whole-image cascade for multi-plant paths, so a three-plant image runs the plant diagnosis work three times, not four.</p>

<p>The point is not to pretend every canopy photo is solvable. The point is to make the output honest about the structure of the image.</p>

<hr/>

<h2 id="what-this-unlocks" id="what-this-unlocks">What this unlocks</h2>

<p>For growers, this makes wide shots more useful. You can upload a photo of a tray and see which plant the diagnosis belongs to.</p>

<p>For paid history, bounding boxes make comparison over time more meaningful. A diagnosis can be stored with the region it came from instead of being attached only to the original image.</p>

<p>For automation, the response is finally shaped like the thing it describes. A controller can loop over plants, display per-plant state, or decide to alert only when any plant crosses a threshold.</p>

<p>For training, this closes a long-standing mismatch. A whole-frame label is often too crude for a multi-plant image. Per-plant boxes let the system learn from the plant region without pretending the entire image has one uniform condition.</p>

<p>This is the main reason I was willing to break the schema. The old response was simpler, but it encoded the wrong assumption.</p>

<hr/>

<h2 id="migration-checklist" id="migration-checklist">Migration checklist</h2>

<p>If you maintain a PlantLab client, check these paths:</p>
<ul><li>Replace reads of top-level <code>is_healthy</code>, <code>health_confidence</code>, <code>growth_stage</code>, <code>conditions</code>, <code>pests</code>, <code>mulders_hypotheses</code>, reasoning fields, and <code>reliability_score</code> with reads from <code>results[]</code>.</li>
<li>Keep reading top-level <code>is_cannabis</code> and <code>cannabis_confidence</code>.</li>
<li>Treat <code>results[0]</code> as the primary plant if you need backward-compatible behavior.</li>
<li>Use <code>len(results)</code> as the plant count.</li>
<li>Draw <code>result.bbox</code> over the original uploaded image if your UI supports overlays.</li>
<li>Treat <code>{x0:0, y0:0, x1:1, y1:1}</code> as the whole-image fallback box.</li>
<li>If you use Home Assistant, update <code>plantlab-ai/home-assistant-plantlab</code> to <code>v0.7.0</code>. Existing diagnosis sensors continue to show the primary plant, and the new <code>sensor.plantlab_plant_count</code> exposes <code>len(results)</code>.</li></ul>

<p>The full OpenAPI schema is available in the PlantLab docs at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, structured JSON responses, and API docs built for automation clients.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>Does every upload now return multiple plants?</strong></p>

<p>No. Single-plant images return one result. Ambiguous dense canopy images may also return one result if splitting would be unsafe.</p>

<p><strong>Did the old fields disappear?</strong></p>

<p>Yes. Per-plant diagnosis fields moved into <code>results[]</code> in schema <code>3.0.0</code>. Top-level <code>is_cannabis</code> and <code>cannabis_confidence</code> remain image-level fields.</p>

<p><strong>How do I get the plant count?</strong></p>

<p>Use <code>response.results.length</code>.</p>

<p><strong>Are the bounding boxes pixel coordinates?</strong></p>

<p>No. They are normalized coordinates from 0 to 1, relative to the original image. Multiply by image width and height when drawing overlays.</p>

<p><strong>What should older clients do?</strong></p>

<p>Read <code>results[0]</code> first. That restores the old “one diagnosis” behavior while keeping your code compatible with multi-plant uploads.</p>

<p><strong>Is the Home Assistant integration ready?</strong></p>

<p>Yes. The official Home Assistant integration is updated in <code>v0.7.0</code>. It reads schema <code>3.0.0</code>, surfaces the primary plant through the existing sensors, adds <code>sensor.plantlab_plant_count</code>, and still tolerates pre-3.0.0 flat API responses during rollout.</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/plantlab-multi-plant-analysis-schema-3</guid>
      <pubDate>Mon, 29 Jun 2026 18:09:17 +0000</pubDate>
    </item>
    <item>
      <title>Honest AI for Plant Health Diagnosis: What a Research Month Taught Me</title>
      <link>https://blog.plantlab.ai/honest-ai-plant-health-diagnosis?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[A locked, checksum-pinned test set of cannabis plant photos feeding a diagnosis pipeline, with a held-out accuracy figure shown next to a benchmark figure&#xA;&#xA;The Short Version&#xA;&#xA;AI plant health diagnosis is having a moment - grow cameras with &#34;AI&#34; on the box, phone apps that name a deficiency from one photo, controllers that promise to read your plants for you. Most of them report a confidence number they haven&#39;t earned, because the hard part of AI plant diagnosis isn&#39;t producing an answer. It&#39;s knowing when the answer is wrong, and proving the accuracy you claim on photos the model has never seen. June at PlantLab was a research-and-hardening month spent almost entirely on that second problem: catching my own model being wrong before a grower could. This is what that looks like from the inside, with the numbers.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;Most of the month, I tried to prove myself wrong&#xA;&#xA;There&#39;s a failure pattern in applied machine learning that&#39;s easy to fall into and embarrassing to admit: you measure your model against data it has secretly already seen, get a great number, and ship a worse product than your benchmark says you have. The honest version of this work is mostly the unglamorous job of making sure that can&#39;t happen - and then re-checking, because it usually has happened somewhere you didn&#39;t look.&#xA;&#xA;So June was light on shipped features and heavy on measurement. Three of the month&#39;s most useful outcomes were negative - things I built, validated, and then deliberately threw away because the evidence said they didn&#39;t help. That&#39;s not wasted time. A NO-GO you can trust is worth more than a feature you can&#39;t.&#xA;&#xA;---&#xA;&#xA;The accuracy number that was lying to me&#xA;&#xA;The biggest single finding of the month: my internal accuracy was inflated by roughly 14 percentage points, and the cause was data leakage.&#xA;&#xA;Here&#39;s what that means in plain terms. To know how good a diagnosis model is, you test it on photos it didn&#39;t learn from. If even a slice of your test photos overlap with your training photos, the model isn&#39;t being asked to diagnose - it&#39;s being asked to remember, and it scores far higher than it will in the real world. When I audited my evaluation set carefully, about 85% of one classifier stage&#39;s test images turned out to share lineage with its training data. The accuracy that overlap was buying us was about 14 points of pure illusion.&#xA;&#xA;The fix was tedious and worth every hour. I rebuilt a clean evaluation set - over 20,000 plant images, locked and checksum-pinned so it can&#39;t drift - with the training lineage of every image traced and excluded. Then I made &#34;score only against the locked, leakage-free set&#34; a mandatory gate that every model has to pass before it can deploy. No new version ships on a number I can&#39;t defend.&#xA;&#xA;On that honest, leakage-free set, overall diagnostic accuracy sits at 94.6%, up from 93.5% at the start of the month. That second number matters more than the first: it&#39;s measured on data the model has never touched, and it went up during a month where I was actively trying to deflate my own claims. Two of the pipeline&#39;s stages remain the weak links and are the explicit target of the next training round - I publish the strong numbers below and keep the weak ones honest rather than hiding them.&#xA;&#xA;For context, the two stages I&#39;m confident citing, measured the same leakage-aware way:&#xA;&#xA;| What it decides | Balanced accuracy | Notes |&#xA;|---|---|---|&#xA;| Is this a cannabis plant at all? | 99.96% | Gate before any diagnosis runs |&#xA;| Is the plant healthy or showing a problem? | 98.4% | The screen most automations act on |&#xA;| Inference speed (full pipeline) | 18 ms | On GPU, per photo |&#xA;&#xA;---&#xA;&#xA;Three things I built, validated, and killed&#xA;&#xA;A diagnosis pipeline gets safer when it knows when to stop and ask for help. So I tried to add three &#34;brakes&#34; - rules that would catch a likely-wrong answer and downgrade it to &#34;not sure&#34; instead of stating it confidently. Building them was the easy part. Testing whether they actually helped is where most of the value was.&#xA;&#xA;The reliability brake shipped. It watches a separate trustworthiness signal and, on the photos where the detailed classifier is most likely to be wrong, hands back a hedge instead of a false certainty. It catches roughly half of the cases that would otherwise have produced a confident wrong answer with no warning. This one earned its place.&#xA;Two other brakes did not. A &#34;health gate&#34; and a second-stage abstention rule both looked promising on paper. Both got built, both got tested against the locked set, and both came back NO-GO: the classifiers they were meant to second-guess are confident enough that a threshold catches almost nothing real while occasionally suppressing correct answers. I removed them.&#xA;&#xA;There was also a retraction. Earlier in the quarter I&#39;d said nutrient problems were my single biggest error source, and that a &#34;nutrient brake&#34; had validated as a win. Re-measuring with the nutrient specialist properly loaded into the test harness overturned both claims. The earlier result had been reading the wrong confidence signal - a bug in the test setup, not a real weakness in the model. Once it was fixed, accuracy went up, nutrient-specific errors dropped by about 55%, and my biggest remaining weak spot turned out to be somewhere else entirely. Walking back a number you&#39;ve already said out loud isn&#39;t comfortable. But a diagnosis company that won&#39;t retract its own bad measurement has no business asking growers to trust its good ones.&#xA;&#xA;---&#xA;&#xA;Why one photo often isn&#39;t enough&#xA;&#xA;A lot of plant problems look alike. That&#39;s not a model limitation - it&#39;s a property of the plant. Different root causes converge on the same visible leaf symptom, which means a single RGB photo sometimes physically does not contain enough information to separate them.&#xA;&#xA;The clearest example is watering. Both overwatering and underwatering can produce yellowing that looks exactly like nitrogen deficiency. Overwatering starves the roots of oxygen, which impairs their ability to take up nutrients; underwatering cuts off the soil-water flow that carries nitrate to the roots in the first place. In both cases the leaf says &#34;nitrogen,&#34; while the real fix is the watering can. The same trap shows up across the deficiency map:&#xA;&#xA;| Looks like | Could actually be | What separates them |&#xA;|---|---|---|&#xA;| Nitrogen deficiency (yellowing lower leaves) | Overwatering or underwatering | Root-zone moisture, not the leaf |&#xA;| Magnesium deficiency | Calcium deficiency | Old/lower leaves (Mg) vs distorted new growth (Ca) |&#xA;| A true deficiency | pH lockout (nutrient present but unavailable) | A pH and EC test, not a photo |&#xA;| Nutrient burn | Light burn | Pattern and location under the lamp |&#xA;&#xA;An honest plant diagnosis tool has to respect this. It&#39;s why PlantLab returns a separate reliability signal for exactly the ambiguous cases, and why I tell integrators to gate automation on that signal rather than on raw confidence (I wrote that up in detail in Confidence Is Not Reliability).&#xA;&#xA;It&#39;s also why I started a new line of work in June on counting and separating plants. I kept seeing photos with more than one plant in frame, and a diagnosis model handed two plants at once can&#39;t give either a clean answer. The first version of that work counts the right number of plants about 70% of the time and lands within one almost 90% of the time, fast enough to run on a CPU - early, but it&#39;s the prerequisite for diagnosing the messy real-world shots people actually take, not just the tidy single-plant ones.&#xA;&#xA;---&#xA;&#xA;The unglamorous half: infrastructure and security&#xA;&#xA;Two larger efforts wrapped up in June that don&#39;t change a single diagnosis but matter for anyone trusting the service.&#xA;&#xA;I finished moving PlantLab entirely onto European infrastructure and tore down the last of the old US cloud. A plant photo is sensitive - it reveals that someone grows, and at scale, how much - so the data path now runs through providers I chose for sovereignty rather than convenience: compute in Germany, CDN in Slovenia, database and email in France. The full reasoning is in Why PlantLab Runs in Europe. I also built and tested a one-command disaster-recovery rebuild against a live clone, so a lost server is a known, rehearsed recovery rather than a panic.&#xA;&#xA;On security, the runtime container moved to a distroless image, dropping its count of known high-and-critical vulnerabilities from ten to zero, alongside a broader hardening pass - secret rotation, host firewalling, supply-chain scanning, and request-path fixes. None of it is visible in a diagnosis response, which is rather the point.&#xA;&#xA;---&#xA;&#xA;What I saw at Mary Jane Berlin&#xA;&#xA;I spent the first weekend of June at Mary Jane Berlin, mostly talking with grow-hardware vendors. The headline impression: the industry has decided AI is the next feature, and the race is on. Multiple vendors had AI grow cameras on display, several of them announced that week. The appetite is real and growing.&#xA;&#xA;What&#39;s missing is the rigor. Across the floor, the AI accuracy figures were vendor-stated and unaudited, the disclosure of how any of it actually works was thin, and the recurring complaint I hear from growers who&#39;ve tried the general-purpose-AI route is the same one every time: it answers with total confidence, it&#39;s frequently wrong, and it gives you nothing to tell the difference. There is clear demand for &#34;AI something&#34; in cultivation. There is very little supply of AI that&#39;s specifically built for the plant, honest about what it can&#39;t see, and willing to publish a number it didn&#39;t cherry-pick.&#xA;&#xA;That gap is the entire reason PlantLab exists, and Berlin made it concrete. Interest in a rigorous, cannabis-specific diagnosis service was easy to find. Several people I spoke with signed up to try it on the spot.&#xA;&#xA;---&#xA;&#xA;Where this leaves PlantLab&#xA;&#xA;A month of trying to prove myself wrong left the product in a better place than a month of shipping features would have. I caught a 14-point measurement illusion, killed two things that didn&#39;t work, retracted a claim that didn&#39;t hold, and came out the other side with accuracy that&#39;s higher and honest - 99.96% on whether a photo is even cannabis, 98.4% on healthy-versus-problem, 94.6% end-to-end, all measured on photos the model has never seen, all at 18 milliseconds.&#xA;&#xA;That&#39;s the bar I think AI plant health diagnosis should clear before it asks a grower to act on it. Most of the tools shipping right now don&#39;t, and they don&#39;t tell you that. I&#39;d rather show my work.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai - three diagnoses a day, results in milliseconds, every diagnosis returns a reliability score so you know when to trust it. API documentation is at plantlab.ai/docs. If you build grow hardware or a cultivation app and want diagnosis that&#39;s actually accountable, the API is built to drop into your stack.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;How accurate is AI plant health diagnosis, really?&#xA;&#xA;It depends entirely on how the accuracy was measured. A number measured on photos the model also trained on is meaningless - it tests memory, not diagnosis. PlantLab&#39;s accuracy is measured on a locked, checksum-pinned test set with all training-related images excluded: 99.96% on whether a photo is cannabis, 98.4% on healthy-versus-problem, and 94.6% end-to-end. Most consumer &#34;AI camera&#34; accuracy figures are vendor-stated and not independently audited.&#xA;&#xA;Can AI diagnose a plant problem from a single photo?&#xA;&#xA;Often, but not always. Different root causes can produce identical-looking leaf symptoms - overwatering, underwatering, and true nitrogen deficiency can all yellow the lower leaves the same way. A responsible tool returns a reliability signal that drops on exactly these ambiguous cases, rather than reporting the same confidence on a clear photo and a hopeless one.&#xA;&#xA;Why do AI grow cameras and plant apps get diagnoses wrong?&#xA;&#xA;The common pattern is wrapping a general-purpose vision model and printing its confidence as if it were an accuracy guarantee. A general model handed a plant photo will produce a confident-looking answer whether or not it has any basis for the call. Tools built specifically for the plant, and calibrated against real outcomes, can instead tell you when they&#39;re unsure.&#xA;&#xA;What&#39;s the difference between confidence and reliability in a diagnosis?&#xA;&#xA;Confidence is how strongly the model picked an answer. Reliability is whether you should act on that answer, on this specific image. They agree on easy photos and diverge on hard ones - which is the whole reason to track reliability separately for any automation. Full explanation here.&#xA;&#xA;Does overwatering cause nitrogen deficiency?&#xA;&#xA;It can produce nitrogen-deficiency-like symptoms. Overwatering deprives roots of oxygen and impairs nutrient uptake, so the plant shows lower-leaf yellowing even when nitrogen is available. Underwatering can cause the same look by cutting off the water flow that delivers nitrate to the roots. In both cases the cause is the watering, not the nutrient - which is why a leaf photo alone can mislead.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;Confidence Is Not Reliability: Trust Signals for Automated Plant Diagnosis&#xA;Why PlantLab Runs in Europe&#xA;How PlantLab&#39;s AI Diagnoses Cannabis Plant Problems in 18 Milliseconds&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/PNnypncy.png" alt="A locked, checksum-pinned test set of cannabis plant photos feeding a diagnosis pipeline, with a held-out accuracy figure shown next to a benchmark figure"/></p>

<h2 id="the-short-version" id="the-short-version">The Short Version</h2>

<p>AI plant health diagnosis is having a moment – grow cameras with “AI” on the box, phone apps that name a deficiency from one photo, controllers that promise to read your plants for you. Most of them report a confidence number they haven&#39;t earned, because the hard part of AI plant diagnosis isn&#39;t producing an answer. It&#39;s knowing when the answer is wrong, and proving the accuracy you claim on photos the model has never seen. June at PlantLab was a research-and-hardening month spent almost entirely on that second problem: catching my own model being wrong before a grower could. This is what that looks like from the inside, with the numbers.</p>



<hr/>

<h2 id="most-of-the-month-i-tried-to-prove-myself-wrong" id="most-of-the-month-i-tried-to-prove-myself-wrong">Most of the month, I tried to prove myself wrong</h2>

<p>There&#39;s a failure pattern in applied machine learning that&#39;s easy to fall into and embarrassing to admit: you measure your model against data it has secretly already seen, get a great number, and ship a worse product than your benchmark says you have. The honest version of this work is mostly the unglamorous job of making sure that can&#39;t happen – and then re-checking, because it usually has happened somewhere you didn&#39;t look.</p>

<p>So June was light on shipped features and heavy on measurement. Three of the month&#39;s most useful outcomes were <em>negative</em> – things I built, validated, and then deliberately threw away because the evidence said they didn&#39;t help. That&#39;s not wasted time. A NO-GO you can trust is worth more than a feature you can&#39;t.</p>

<hr/>

<h2 id="the-accuracy-number-that-was-lying-to-me" id="the-accuracy-number-that-was-lying-to-me">The accuracy number that was lying to me</h2>

<p>The biggest single finding of the month: my internal accuracy was inflated by roughly 14 percentage points, and the cause was data leakage.</p>

<p>Here&#39;s what that means in plain terms. To know how good a diagnosis model is, you test it on photos it didn&#39;t learn from. If even a slice of your test photos overlap with your training photos, the model isn&#39;t being asked to diagnose – it&#39;s being asked to remember, and it scores far higher than it will in the real world. When I audited my evaluation set carefully, about 85% of one classifier stage&#39;s test images turned out to share lineage with its training data. The accuracy that overlap was buying us was about 14 points of pure illusion.</p>

<p>The fix was tedious and worth every hour. I rebuilt a clean evaluation set – over 20,000 plant images, locked and checksum-pinned so it can&#39;t drift – with the training lineage of every image traced and excluded. Then I made “score only against the locked, leakage-free set” a mandatory gate that every model has to pass before it can deploy. No new version ships on a number I can&#39;t defend.</p>

<p>On that honest, leakage-free set, overall diagnostic accuracy sits at 94.6%, up from 93.5% at the start of the month. That second number matters more than the first: it&#39;s measured on data the model has never touched, and it went <em>up</em> during a month where I was actively trying to deflate my own claims. Two of the pipeline&#39;s stages remain the weak links and are the explicit target of the next training round – I publish the strong numbers below and keep the weak ones honest rather than hiding them.</p>

<p>For context, the two stages I&#39;m confident citing, measured the same leakage-aware way:</p>

<table>
<thead>
<tr>
<th>What it decides</th>
<th>Balanced accuracy</th>
<th>Notes</th>
</tr>
</thead>

<tbody>
<tr>
<td>Is this a cannabis plant at all?</td>
<td>99.96%</td>
<td>Gate before any diagnosis runs</td>
</tr>

<tr>
<td>Is the plant healthy or showing a problem?</td>
<td>98.4%</td>
<td>The screen most automations act on</td>
</tr>

<tr>
<td>Inference speed (full pipeline)</td>
<td>18 ms</td>
<td>On GPU, per photo</td>
</tr>
</tbody>
</table>

<hr/>

<h2 id="three-things-i-built-validated-and-killed" id="three-things-i-built-validated-and-killed">Three things I built, validated, and killed</h2>

<p>A diagnosis pipeline gets safer when it knows when to stop and ask for help. So I tried to add three “brakes” – rules that would catch a likely-wrong answer and downgrade it to “not sure” instead of stating it confidently. Building them was the easy part. Testing whether they actually helped is where most of the value was.</p>
<ul><li><strong>The reliability brake shipped.</strong> It watches a separate trustworthiness signal and, on the photos where the detailed classifier is most likely to be wrong, hands back a hedge instead of a false certainty. It catches roughly half of the cases that would otherwise have produced a confident wrong answer with no warning. This one earned its place.</li>
<li><strong>Two other brakes did not.</strong> A “health gate” and a second-stage abstention rule both looked promising on paper. Both got built, both got tested against the locked set, and both came back NO-GO: the classifiers they were meant to second-guess are confident enough that a threshold catches almost nothing real while occasionally suppressing correct answers. I removed them.</li></ul>

<p>There was also a retraction. Earlier in the quarter I&#39;d said nutrient problems were my single biggest error source, and that a “nutrient brake” had validated as a win. Re-measuring with the nutrient specialist properly loaded into the test harness overturned both claims. The earlier result had been reading the wrong confidence signal – a bug in the test setup, not a real weakness in the model. Once it was fixed, accuracy went up, nutrient-specific errors dropped by about 55%, and my biggest remaining weak spot turned out to be somewhere else entirely. Walking back a number you&#39;ve already said out loud isn&#39;t comfortable. But a diagnosis company that won&#39;t retract its own bad measurement has no business asking growers to trust its good ones.</p>

<hr/>

<h2 id="why-one-photo-often-isn-t-enough" id="why-one-photo-often-isn-t-enough">Why one photo often isn&#39;t enough</h2>

<p>A lot of plant problems look alike. That&#39;s not a model limitation – it&#39;s a property of the plant. Different root causes converge on the same visible leaf symptom, which means a single RGB photo sometimes physically does not contain enough information to separate them.</p>

<p>The clearest example is watering. Both overwatering and underwatering can produce yellowing that looks exactly like nitrogen deficiency. Overwatering starves the roots of oxygen, which impairs their ability to take up nutrients; underwatering cuts off the soil-water flow that carries nitrate to the roots in the first place. In both cases the leaf says “nitrogen,” while the real fix is the watering can. The same trap shows up across the deficiency map:</p>

<table>
<thead>
<tr>
<th>Looks like</th>
<th>Could actually be</th>
<th>What separates them</th>
</tr>
</thead>

<tbody>
<tr>
<td>Nitrogen deficiency (yellowing lower leaves)</td>
<td>Overwatering or underwatering</td>
<td>Root-zone moisture, not the leaf</td>
</tr>

<tr>
<td>Magnesium deficiency</td>
<td>Calcium deficiency</td>
<td>Old/lower leaves (Mg) vs distorted new growth (Ca)</td>
</tr>

<tr>
<td>A true deficiency</td>
<td>pH lockout (nutrient present but unavailable)</td>
<td>A pH and EC test, not a photo</td>
</tr>

<tr>
<td>Nutrient burn</td>
<td>Light burn</td>
<td>Pattern and location under the lamp</td>
</tr>
</tbody>
</table>

<p>An honest plant diagnosis tool has to respect this. It&#39;s why PlantLab returns a separate reliability signal for exactly the ambiguous cases, and why I tell integrators to gate automation on that signal rather than on raw confidence (I wrote that up in detail in <a href="https://blog.plantlab.ai/confidence-is-not-reliability">Confidence Is Not Reliability</a>).</p>

<p>It&#39;s also why I started a new line of work in June on <strong>counting and separating plants</strong>. I kept seeing photos with more than one plant in frame, and a diagnosis model handed two plants at once can&#39;t give either a clean answer. The first version of that work counts the right number of plants about 70% of the time and lands within one almost 90% of the time, fast enough to run on a CPU – early, but it&#39;s the prerequisite for diagnosing the messy real-world shots people actually take, not just the tidy single-plant ones.</p>

<hr/>

<h2 id="the-unglamorous-half-infrastructure-and-security" id="the-unglamorous-half-infrastructure-and-security">The unglamorous half: infrastructure and security</h2>

<p>Two larger efforts wrapped up in June that don&#39;t change a single diagnosis but matter for anyone trusting the service.</p>

<p>I finished moving PlantLab entirely onto European infrastructure and tore down the last of the old US cloud. A plant photo is sensitive – it reveals that someone grows, and at scale, how much – so the data path now runs through providers I chose for sovereignty rather than convenience: compute in Germany, CDN in Slovenia, database and email in France. The full reasoning is in <a href="https://blog.plantlab.ai/plantlab-runs-in-europe">Why PlantLab Runs in Europe</a>. I also built and tested a one-command disaster-recovery rebuild against a live clone, so a lost server is a known, rehearsed recovery rather than a panic.</p>

<p>On security, the runtime container moved to a distroless image, dropping its count of known high-and-critical vulnerabilities from ten to zero, alongside a broader hardening pass – secret rotation, host firewalling, supply-chain scanning, and request-path fixes. None of it is visible in a diagnosis response, which is rather the point.</p>

<hr/>

<h2 id="what-i-saw-at-mary-jane-berlin" id="what-i-saw-at-mary-jane-berlin">What I saw at Mary Jane Berlin</h2>

<p>I spent the first weekend of June at Mary Jane Berlin, mostly talking with grow-hardware vendors. The headline impression: the industry has decided AI is the next feature, and the race is on. Multiple vendors had AI grow cameras on display, several of them announced that week. The appetite is real and growing.</p>

<p>What&#39;s missing is the rigor. Across the floor, the AI accuracy figures were vendor-stated and unaudited, the disclosure of how any of it actually works was thin, and the recurring complaint I hear from growers who&#39;ve tried the general-purpose-AI route is the same one every time: it answers with total confidence, it&#39;s frequently wrong, and it gives you nothing to tell the difference. There is clear demand for “AI something” in cultivation. There is very little supply of AI that&#39;s specifically built for the plant, honest about what it can&#39;t see, and willing to publish a number it didn&#39;t cherry-pick.</p>

<p>That gap is the entire reason PlantLab exists, and Berlin made it concrete. Interest in a rigorous, cannabis-specific diagnosis service was easy to find. Several people I spoke with signed up to try it on the spot.</p>

<hr/>

<h2 id="where-this-leaves-plantlab" id="where-this-leaves-plantlab">Where this leaves PlantLab</h2>

<p>A month of trying to prove myself wrong left the product in a better place than a month of shipping features would have. I caught a 14-point measurement illusion, killed two things that didn&#39;t work, retracted a claim that didn&#39;t hold, and came out the other side with accuracy that&#39;s higher <em>and</em> honest – 99.96% on whether a photo is even cannabis, 98.4% on healthy-versus-problem, 94.6% end-to-end, all measured on photos the model has never seen, all at 18 milliseconds.</p>

<p>That&#39;s the bar I think AI plant health diagnosis should clear before it asks a grower to act on it. Most of the tools shipping right now don&#39;t, and they don&#39;t tell you that. I&#39;d rather show my work.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a> – three diagnoses a day, results in milliseconds, every diagnosis returns a reliability score so you know when to trust it. API documentation is at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>. If you build grow hardware or a cultivation app and want diagnosis that&#39;s actually accountable, the API is built to drop into your stack.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>How accurate is AI plant health diagnosis, really?</strong></p>

<p>It depends entirely on how the accuracy was measured. A number measured on photos the model also trained on is meaningless – it tests memory, not diagnosis. PlantLab&#39;s accuracy is measured on a locked, checksum-pinned test set with all training-related images excluded: 99.96% on whether a photo is cannabis, 98.4% on healthy-versus-problem, and 94.6% end-to-end. Most consumer “AI camera” accuracy figures are vendor-stated and not independently audited.</p>

<p><strong>Can AI diagnose a plant problem from a single photo?</strong></p>

<p>Often, but not always. Different root causes can produce identical-looking leaf symptoms – overwatering, underwatering, and true nitrogen deficiency can all yellow the lower leaves the same way. A responsible tool returns a reliability signal that drops on exactly these ambiguous cases, rather than reporting the same confidence on a clear photo and a hopeless one.</p>

<p><strong>Why do AI grow cameras and plant apps get diagnoses wrong?</strong></p>

<p>The common pattern is wrapping a general-purpose vision model and printing its confidence as if it were an accuracy guarantee. A general model handed a plant photo will produce a confident-looking answer whether or not it has any basis for the call. Tools built specifically for the plant, and calibrated against real outcomes, can instead tell you when they&#39;re unsure.</p>

<p><strong>What&#39;s the difference between confidence and reliability in a diagnosis?</strong></p>

<p>Confidence is how strongly the model picked an answer. Reliability is whether you should act on that answer, on this specific image. They agree on easy photos and diverge on hard ones – which is the whole reason to track reliability separately for any automation. <a href="https://blog.plantlab.ai/confidence-is-not-reliability">Full explanation here</a>.</p>

<p><strong>Does overwatering cause nitrogen deficiency?</strong></p>

<p>It can produce nitrogen-deficiency-like symptoms. Overwatering deprives roots of oxygen and impairs nutrient uptake, so the plant shows lower-leaf yellowing even when nitrogen is available. Underwatering can cause the same look by cutting off the water flow that delivers nitrate to the roots. In both cases the cause is the watering, not the nutrient – which is why a leaf photo alone can mislead.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/confidence-is-not-reliability">Confidence Is Not Reliability: Trust Signals for Automated Plant Diagnosis</a>
– <a href="https://blog.plantlab.ai/plantlab-runs-in-europe">Why PlantLab Runs in Europe</a>
– <a href="https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds">How PlantLab&#39;s AI Diagnoses Cannabis Plant Problems in 18 Milliseconds</a></p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/honest-ai-plant-health-diagnosis</guid>
      <pubDate>Fri, 26 Jun 2026 16:14:15 +0000</pubDate>
    </item>
    <item>
      <title>Confidence Is Not Reliability: Trust Signals for Automated Plant Diagnosis</title>
      <link>https://blog.plantlab.ai/confidence-is-not-reliability?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[API response JSON showing high confidence but a low reliabilityscore, mapped onto a three-band trust gauge&#xA;&#xA;The Short Version&#xA;&#xA;Most plant diagnosis tools show you a confidence number. Confidence tells you how strongly the model picked an answer - not whether you should act on it. Those are different questions, and on a hard photo they get different answers: a model can be very confident and flat wrong. If you&#39;re feeding a diagnosis into an automation - a Home Assistant flow, a grow-room controller, a dashboard alert - the signal you actually want is reliability: how trustworthy is this answer, on this specific image? PlantLab returns both. Here&#39;s how to use each, and why automation should gate on reliability, not confidence.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;The failure mode nobody warns you about&#xA;&#xA;Here&#39;s the trap. You wire a plant diagnosis API into an automation. When the model says &#34;magnesium deficiency&#34; with 0.9 confidence, you trigger an action - a notification, a nutrient-dosing routine, a log entry. It works in testing. The clear photos you tested with all return high confidence and correct answers.&#xA;&#xA;Then a real photo arrives: backlit, half in shadow, two plants in frame, taken at midnight because something looked off. The model returns &#34;magnesium deficiency&#34; at 0.9 confidence again. Except this time it&#39;s wrong, because that confidence number was never a promise about correctness. It was a statement about how strongly the model leaned toward one class. On an ambiguous image, the model can lean hard in the wrong direction and report a high number while doing it.&#xA;&#xA;A confidence score that&#39;s only trustworthy on easy photos isn&#39;t a trust signal. It&#39;s a confidence display. And the cases where you most need to know whether to trust an answer are exactly the cases where raw confidence is least informative.&#xA;&#xA;This isn&#39;t hypothetical. A wave of consumer photo-diagnosis apps has shipped in the last year, and most of them do the same thing: wrap a general-purpose vision model and print a &#34;confidence&#34; percentage next to the answer. The problem is that a general-purpose model handed a plant photo will produce a confident-looking number whether or not it has any real basis for the call - the percentage reflects how hard the model leaned, not how often it&#39;s right when it leans that hard. A confidence figure with nothing calibrating it against actual outcomes is decoration. It looks like a trust signal and behaves like one right up until the photo gets hard, which is the moment you needed it most.&#xA;&#xA;---&#xA;&#xA;Three different questions&#xA;&#xA;The fix is to stop treating &#34;confidence&#34; as one signal and recognize that an automated diagnosis is really answering three separate questions.&#xA;&#xA;| Signal | The question it answers | Where it lives |&#xA;|--------|------------------------|----------------|&#xA;| Per-class confidence | &#34;How strongly did the model pick each condition?&#34; | confidence on each item in conditions[] |&#xA;| Reliability | &#34;Should this whole diagnosis be acted on, on this image?&#34; | reliabilityscore on the response |&#xA;| Policy threshold | &#34;What does my system do at each trust level?&#34; | Your code |&#xA;&#xA;Per-class confidence is the model&#39;s strength of preference for a specific condition. It&#39;s useful when you want to know what the model saw and how it ranked the possibilities. It is not a verdict on whether the overall answer is safe to act on.&#xA;&#xA;Reliability is a single number from 0 to 1 that estimates how trustworthy the entire diagnosis is on this particular image. It&#39;s the signal that stays informative on the hard cases - the ambiguous, badly-lit, multi-plant photos where you most need to know whether to trust the result. Higher is better. Low means &#34;something about this image makes the answer shaky, double-check before acting.&#34;&#xA;&#xA;The policy threshold is yours. The API gives you a number; your system decides what to do at each level. That decision is a product choice, not a model output, and it&#39;s where the actual safety lives.&#xA;&#xA;---&#xA;&#xA;Wiring automation to the right signal&#xA;&#xA;The practical rule for any automation: branch on reliability, then use per-class confidence for the details.&#xA;&#xA;A reasonable starting policy, which you should tune to your own tolerance for false actions:&#xA;&#xA;High reliability (say, above 0.7): safe to act automatically. Update the dashboard, log the plant state, trigger the routine. This is the band where automating saves you work without costing you trust.&#xA;Middle reliability (roughly 0.3 to 0.7): don&#39;t act blind. This is the band to ask for a second photo, queue the case for a human glance, or surface a &#34;check this one&#34; flag instead of firing an action.&#xA;Low reliability (below 0.3): do not automate. Show the result as informational at most. Acting on a low-reliability diagnosis is how an automation does the wrong thing confidently.&#xA;&#xA;The reason this ordering matters: per-class confidence will happily hand you a high number in all three bands. Reliability is what separates them. If you gate automation on confidence alone, you&#39;ll take action on the 2 AM backlit photo. If you gate on reliability, that photo lands in the &#34;ask for another shot&#34; band where it belongs.&#xA;&#xA;For an integrator, this also means you don&#39;t have to build your own uncertainty handling from scratch. The hard part - estimating whether an answer holds up on a given image - is in the response. Your job is to pick the cutoffs that match what your automation does when it&#39;s wrong.&#xA;&#xA;---&#xA;&#xA;Why PlantLab returns a single reliability number&#xA;&#xA;PlantLab used to return rule-based trust fields derived from the model&#39;s confidences. Those were removed in favor of a single learned reliability signal, and the API schema was bumped to make the change loud rather than silent. The full migration writeup is linked below.&#xA;&#xA;The short version of why: a trust signal assembled from a handful of rules works on easy cases and falls apart on hard ones, which is the opposite of what you want. A trust signal is only earning its keep on the ambiguous cases - the ones where the answer genuinely might be wrong. So PlantLab consolidated to one number, on one contract: a 0-to-1 reliability score, present whenever the API returns a condition diagnosis, designed to stay honest on the cases that matter.&#xA;&#xA;One implementation note worth knowing: reliability is omitted when there&#39;s no condition diagnosis to score - for a non-cannabis photo, or a healthy plant. Treat a missing score as &#34;no score available,&#34; not as a low score. Your automation logic should handle absence explicitly rather than defaulting it to zero.&#xA;&#xA;---&#xA;&#xA;The product principle underneath&#xA;&#xA;A diagnosis API that always answers, always confidently, is easy to build and dangerous to automate against. The harder thing - and the one I think actually matters - is an API that knows when to slow you down, that can say, in effect, &#34;I have an answer, but this image is the kind where I&#39;m often wrong, so don&#39;t wire a pump to this one.&#34;&#xA;&#xA;That&#39;s what the confidence-versus-reliability distinction is really about. Confidence is the model talking about itself. Reliability is the system telling you whether to listen. For anything beyond a human reading a single result on a screen - and especially for automation that acts without a person in the loop - reliability is the number to build on.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, results in milliseconds. Every diagnosis returns a reliability score; the full API documentation lives at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;What&#39;s the difference between confidence and reliability?&#xA;&#xA;Per-class confidence is how strongly the model picked a specific condition. Reliability is how trustworthy the whole diagnosis is on this particular image. A model can be highly confident in a wrong answer on an ambiguous photo - which is exactly why you want a separate reliability signal for automation decisions.&#xA;&#xA;Which signal should I use to trigger an automation?&#xA;&#xA;Reliability. Gate the automation on the reliability score (act high, verify middle, don&#39;t automate low), then use per-class confidence for the details of what to display or log. Gating on confidence alone will take action on hard photos where confidence is high but the answer isn&#39;t trustworthy.&#xA;&#xA;What thresholds should I use?&#xA;&#xA;A reasonable starting point is above 0.7 for automatic action, 0.3 to 0.7 for &#34;ask for another photo or a human check,&#34; and below 0.3 for &#34;don&#39;t automate.&#34; These are starting values - tune them to how costly a wrong action is in your setup.&#xA;&#xA;What if the reliability score is missing?&#xA;&#xA;It&#39;s omitted when there&#39;s no condition diagnosis to score - a non-cannabis photo or a healthy plant. Treat absence as &#34;no score available,&#34; not as a low score, and handle it explicitly in your code.&#xA;&#xA;Does a high confidence number mean the diagnosis is correct?&#xA;&#xA;No. Confidence reflects how strongly the model leaned toward an answer, not whether the answer is right. On clear photos the two usually agree; on ambiguous ones they can diverge, which is the whole reason reliability exists as a separate signal.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;How PlantLab Knows When It Might Be Wrong: The reliability_score Field - The schema change and one-line migration&#xA;How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds - The pipeline behind the API&#xA;Build an Autonomous Plant Health Monitor with AI + Home Assistant - Where reliability thresholds earn their keep&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/Dx6m9QUp.png" alt="API response JSON showing high confidence but a low reliability_score, mapped onto a three-band trust gauge"/></p>

<h2 id="the-short-version" id="the-short-version">The Short Version</h2>

<p>Most plant diagnosis tools show you a confidence number. Confidence tells you how strongly the model picked an answer – not whether you should act on it. Those are different questions, and on a hard photo they get different answers: a model can be very confident and flat wrong. If you&#39;re feeding a diagnosis into an automation – a Home Assistant flow, a grow-room controller, a dashboard alert – the signal you actually want is reliability: how trustworthy is this answer, on this specific image? PlantLab returns both. Here&#39;s how to use each, and why automation should gate on reliability, not confidence.</p>



<hr/>

<h2 id="the-failure-mode-nobody-warns-you-about" id="the-failure-mode-nobody-warns-you-about">The failure mode nobody warns you about</h2>

<p>Here&#39;s the trap. You wire a plant diagnosis API into an automation. When the model says “magnesium deficiency” with 0.9 confidence, you trigger an action – a notification, a nutrient-dosing routine, a log entry. It works in testing. The clear photos you tested with all return high confidence and correct answers.</p>

<p>Then a real photo arrives: backlit, half in shadow, two plants in frame, taken at midnight because something looked off. The model returns “magnesium deficiency” at 0.9 confidence again. Except this time it&#39;s wrong, because that confidence number was never a promise about correctness. It was a statement about how strongly the model leaned toward one class. On an ambiguous image, the model can lean hard in the wrong direction and report a high number while doing it.</p>

<p>A confidence score that&#39;s only trustworthy on easy photos isn&#39;t a trust signal. It&#39;s a confidence display. And the cases where you most need to know whether to trust an answer are exactly the cases where raw confidence is least informative.</p>

<p>This isn&#39;t hypothetical. A wave of consumer photo-diagnosis apps has shipped in the last year, and most of them do the same thing: wrap a general-purpose vision model and print a “confidence” percentage next to the answer. The problem is that a general-purpose model handed a plant photo will produce a confident-looking number whether or not it has any real basis for the call – the percentage reflects how hard the model leaned, not how often it&#39;s right when it leans that hard. A confidence figure with nothing calibrating it against actual outcomes is decoration. It looks like a trust signal and behaves like one right up until the photo gets hard, which is the moment you needed it most.</p>

<hr/>

<h2 id="three-different-questions" id="three-different-questions">Three different questions</h2>

<p>The fix is to stop treating “confidence” as one signal and recognize that an automated diagnosis is really answering three separate questions.</p>

<table>
<thead>
<tr>
<th>Signal</th>
<th>The question it answers</th>
<th>Where it lives</th>
</tr>
</thead>

<tbody>
<tr>
<td>Per-class confidence</td>
<td>“How strongly did the model pick each condition?”</td>
<td><code>confidence</code> on each item in <code>conditions[]</code></td>
</tr>

<tr>
<td>Reliability</td>
<td>“Should this whole diagnosis be acted on, on this image?”</td>
<td><code>reliability_score</code> on the response</td>
</tr>

<tr>
<td>Policy threshold</td>
<td>“What does my system do at each trust level?”</td>
<td>Your code</td>
</tr>
</tbody>
</table>

<p><strong>Per-class confidence</strong> is the model&#39;s strength of preference for a specific condition. It&#39;s useful when you want to know what the model saw and how it ranked the possibilities. It is not a verdict on whether the overall answer is safe to act on.</p>

<p><strong>Reliability</strong> is a single number from 0 to 1 that estimates how trustworthy the entire diagnosis is on this particular image. It&#39;s the signal that stays informative on the hard cases – the ambiguous, badly-lit, multi-plant photos where you most need to know whether to trust the result. Higher is better. Low means “something about this image makes the answer shaky, double-check before acting.”</p>

<p><strong>The policy threshold</strong> is yours. The API gives you a number; your system decides what to do at each level. That decision is a product choice, not a model output, and it&#39;s where the actual safety lives.</p>

<hr/>

<h2 id="wiring-automation-to-the-right-signal" id="wiring-automation-to-the-right-signal">Wiring automation to the right signal</h2>

<p>The practical rule for any automation: branch on reliability, then use per-class confidence for the details.</p>

<p>A reasonable starting policy, which you should tune to your own tolerance for false actions:</p>
<ul><li><strong>High reliability</strong> (say, above 0.7): safe to act automatically. Update the dashboard, log the plant state, trigger the routine. This is the band where automating saves you work without costing you trust.</li>
<li><strong>Middle reliability</strong> (roughly 0.3 to 0.7): don&#39;t act blind. This is the band to ask for a second photo, queue the case for a human glance, or surface a “check this one” flag instead of firing an action.</li>
<li><strong>Low reliability</strong> (below 0.3): do not automate. Show the result as informational at most. Acting on a low-reliability diagnosis is how an automation does the wrong thing confidently.</li></ul>

<p>The reason this ordering matters: per-class confidence will happily hand you a high number in all three bands. Reliability is what separates them. If you gate automation on confidence alone, you&#39;ll take action on the 2 AM backlit photo. If you gate on reliability, that photo lands in the “ask for another shot” band where it belongs.</p>

<p>For an integrator, this also means you don&#39;t have to build your own uncertainty handling from scratch. The hard part – estimating whether an answer holds up on a given image – is in the response. Your job is to pick the cutoffs that match what your automation does when it&#39;s wrong.</p>

<hr/>

<h2 id="why-plantlab-returns-a-single-reliability-number" id="why-plantlab-returns-a-single-reliability-number">Why PlantLab returns a single reliability number</h2>

<p>PlantLab used to return rule-based trust fields derived from the model&#39;s confidences. Those were removed in favor of a single learned reliability signal, and the API schema was bumped to make the change loud rather than silent. The full migration writeup is linked below.</p>

<p>The short version of why: a trust signal assembled from a handful of rules works on easy cases and falls apart on hard ones, which is the opposite of what you want. A trust signal is only earning its keep on the ambiguous cases – the ones where the answer genuinely might be wrong. So PlantLab consolidated to one number, on one contract: a 0-to-1 reliability score, present whenever the API returns a condition diagnosis, designed to stay honest on the cases that matter.</p>

<p>One implementation note worth knowing: reliability is omitted when there&#39;s no condition diagnosis to score – for a non-cannabis photo, or a healthy plant. Treat a missing score as “no score available,” not as a low score. Your automation logic should handle absence explicitly rather than defaulting it to zero.</p>

<hr/>

<h2 id="the-product-principle-underneath" id="the-product-principle-underneath">The product principle underneath</h2>

<p>A diagnosis API that always answers, always confidently, is easy to build and dangerous to automate against. The harder thing – and the one I think actually matters – is an API that knows when to slow you down, that can say, in effect, “I have an answer, but this image is the kind where I&#39;m often wrong, so don&#39;t wire a pump to this one.”</p>

<p>That&#39;s what the confidence-versus-reliability distinction is really about. Confidence is the model talking about itself. Reliability is the system telling you whether to listen. For anything beyond a human reading a single result on a screen – and especially for automation that acts without a person in the loop – reliability is the number to build on.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, results in milliseconds. Every diagnosis returns a reliability score; the full API documentation lives at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>What&#39;s the difference between confidence and reliability?</strong></p>

<p>Per-class confidence is how strongly the model picked a specific condition. Reliability is how trustworthy the whole diagnosis is on this particular image. A model can be highly confident in a wrong answer on an ambiguous photo – which is exactly why you want a separate reliability signal for automation decisions.</p>

<p><strong>Which signal should I use to trigger an automation?</strong></p>

<p>Reliability. Gate the automation on the reliability score (act high, verify middle, don&#39;t automate low), then use per-class confidence for the details of what to display or log. Gating on confidence alone will take action on hard photos where confidence is high but the answer isn&#39;t trustworthy.</p>

<p><strong>What thresholds should I use?</strong></p>

<p>A reasonable starting point is above 0.7 for automatic action, 0.3 to 0.7 for “ask for another photo or a human check,” and below 0.3 for “don&#39;t automate.” These are starting values – tune them to how costly a wrong action is in your setup.</p>

<p><strong>What if the reliability score is missing?</strong></p>

<p>It&#39;s omitted when there&#39;s no condition diagnosis to score – a non-cannabis photo or a healthy plant. Treat absence as “no score available,” not as a low score, and handle it explicitly in your code.</p>

<p><strong>Does a high confidence number mean the diagnosis is correct?</strong></p>

<p>No. Confidence reflects how strongly the model leaned toward an answer, not whether the answer is right. On clear photos the two usually agree; on ambiguous ones they can diverge, which is the whole reason reliability exists as a separate signal.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score">How PlantLab Knows When It Might Be Wrong: The reliability_score Field</a> – The schema change and one-line migration
– <a href="https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds">How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds</a> – The pipeline behind the API
– <a href="https://blog.plantlab.ai/home-assistant-plant-monitoring">Build an Autonomous Plant Health Monitor with AI + Home Assistant</a> – Where reliability thresholds earn their keep</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/confidence-is-not-reliability</guid>
      <pubDate>Wed, 10 Jun 2026 11:02:34 +0000</pubDate>
    </item>
    <item>
      <title>Why PlantLab Runs on European Infrastructure</title>
      <link>https://blog.plantlab.ai/plantlab-runs-in-europe?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Map of Europe rendered in PlantLab&#39;s dark interface palette with bright markers over Germany, Slovenia, and France, representing distributed EU plant diagnosis infrastructure&#xA;&#xA;Building on American cloud is the easy choice, which is exactly why almost everyone makes it. One account at Amazon, Google, or Microsoft and you get the whole stack in one place: compute, database, storage, DNS, email, all of it wired together, billed on one invoice, documented to death. It is a genuinely excellent product. It is also where PlantLab started.&#xA;&#xA;It doesn&#39;t run there anymore. The diagnosis API, the database that holds your history, and the services around them now run on European infrastructure, and the live path a request travels - upload, inference, response, storage - never leaves the EU. That was not the easy choice. I want to explain why I think the easy choice and the right one were two different things here.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;The all-in-one pitch is real, and it&#39;s the trap&#xA;&#xA;The reason a hyperscaler is so easy to build on is that it has already solved the hard part for you: integration. You don&#39;t think about how your database talks to your storage, or whether your email provider and your DNS provider have ever heard of each other. One vendor owns all of it, so it all just fits. For a solo founder with limited hours, that convenience is worth a lot.&#xA;&#xA;The catch is what you hand over in exchange. Your data, and the rules that govern it, now live inside one American company&#39;s estate, under one country&#39;s legal reach, no matter which &#34;region&#34; you tick in the console. An EU region of a US cloud is still a US cloud. The convenience and the loss of sovereignty are the same decision - you can&#39;t take one without the other.&#xA;&#xA;For a lot of software that trade is fine. For a tool that gets handed photos from real, often licensed, grow rooms, I didn&#39;t think it was.&#xA;&#xA;---&#xA;&#xA;Europe is fractured, and that&#39;s the whole point&#xA;&#xA;Here is the honest part nobody puts in the brochure: there is no European hyperscaler. There is no single EU console where you tick a box and get a sovereign, all-in-one stack. So you build it the hard way - you assemble it.&#xA;&#xA;You host compute with one provider in one country. You find a separate DNS and content-delivery company in another. You put your database and transactional email with providers in a third. And before you trust any of them you do the unglamorous research: is this company actually European-owned and European-hosted, or is it a US firm with an EU postcode? Where does the data physically sit? Who can be compelled to hand it over, and under whose law? More vendors, more contracts, more reading, more things that can break.&#xA;&#xA;That fragmentation is real, and it is the cost of sovereignty. The single invoice is convenient precisely because one entity controls everything. The moment you insist that no single entity should, you inherit the work of stitching independent pieces together yourself. I think that work is the point, not a bug to be engineered away.&#xA;&#xA;---&#xA;&#xA;What the assembling actually produced&#xA;&#xA;Here is where things stand now. Three countries, several independent European providers, deliberately not one console:&#xA;&#xA;| Layer | Where it runs now |&#xA;|---|---|&#xA;| Diagnosis API (the inference itself) | Germany |&#xA;| Database (your history, account, keys) | France |&#xA;| Transactional email | France |&#xA;| DNS and content delivery | Slovenia |&#xA;| Web analytics | EU-hosted, cookieless |&#xA;| Uptime monitoring | France |&#xA;&#xA;The cutover was the careful kind. I ran the new European stack alongside the old one, sent real traffic through it, and verified end to end that a diagnosis written on the new infrastructure could be read back correctly, including the parts that are encrypted at rest. Only then did production point at it. The old environment is still sitting there, frozen, as a rollback target for a while longer, because turning the lights off the same day you cut over is how you turn a migration into an incident.&#xA;&#xA;In an earlier post on data privacy I said this move was in progress and was careful not to overclaim it - the core diagnosis API still ran on a US cloud at the time. That caveat is gone now. The whole live path is European.&#xA;&#xA;---&#xA;&#xA;Why the hard road&#xA;&#xA;I didn&#39;t do this for a marketing line, and I&#39;m wary of anyone who treats &#34;EU-hosted&#34; as a badge. I did it because of what a plant photo actually is.&#xA;&#xA;A photo of a flowering plant gives things away - that you grow, the kind of setup you run, and across enough images, the scale of it. For a licensed European operation that is commercially sensitive information sitting inside a regulatory frame. The question that operator asks before sending anything real is simple: where does my data live, and whose rules govern it? &#34;On servers in Germany, under EU law, with no single foreign company holding the whole stack&#34; is a different answer than &#34;somewhere in a US cloud&#39;s European region.&#34;&#xA;&#xA;There is a regulatory tailwind too. Europe&#39;s high-risk AI obligations come into force in August 2026, and the broader direction on privacy keeps moving toward stronger consent and more transparency, not less. Building here now, while PlantLab is small and the change is cheap, beats retrofitting it under a deadline later. But the regulation is the tailwind, not the reason. None of this makes PlantLab a compliance product, and you should distrust any small tool that claims a certificate.&#xA;&#xA;The reason is plainer than that. Data sovereignty, privacy, and digital rights belong to the person whose data it is - not to whichever cloud happens to be cheapest to build on. Most companies build on US infrastructure because it&#39;s easy and it works, and I understand why. I took the harder, more fragmented road because, for a tool handling this kind of data, the user is the one who matters most. The convenience was mine to give up. The data was never mine to be casual with.&#xA;&#xA;---&#xA;&#xA;What this changes for you&#xA;&#xA;For most people using the API, the answer is: nothing you have to do, which is the point. The endpoints are the same, your API key is the same, the response format is the same. Inference still runs in milliseconds - the model didn&#39;t change, only the building it runs in. A migration you have to think about is a migration done badly.&#xA;&#xA;What it changes is what&#39;s true underneath:&#xA;&#xA;Your diagnosis data is processed and stored in the EU. The live request path stays inside European infrastructure from upload to response, across providers that no single foreign entity controls.&#xA;Sovereignty is distributed on purpose. No one company holds your data, your DNS, and your delivery layer at once. That&#39;s harder to run and harder to compromise wholesale.&#xA;The privacy controls travel with it. Bounded, opt-in retention and encryption of the sensitive diagnosis fields ride on top of the move. Where your data lives and how it&#39;s held now point the same way.&#xA;&#xA;If you opt in to contributing diagnoses on the free tier, those images are kept in EU storage as well. The default is still minimization: opt-in, bounded, then deleted.&#xA;&#xA;---&#xA;&#xA;Stated accurately, not stretched&#xA;&#xA;&#34;EU-based&#34; is a phrase that gets stretched until it means a billing address. I&#39;d rather it mean something concrete. Here it does: the request that carries your plant photo is served from Germany, your history is stored in the EU, traffic is measured by cookieless EU analytics, and uptime is watched by EU monitoring - each from an independent European provider. The live path your data travels, in real time, is European end to end. That&#39;s the claim, and it&#39;s a specific one.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, results in milliseconds. The full API documentation, including data handling details, lives at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;Where does PlantLab run now?&#xA;&#xA;The diagnosis API is served from Germany, the database and transactional email are in France, and DNS and content delivery run from Slovenia. Analytics are cookieless and EU-hosted, and uptime monitoring runs from France - each from an independent European provider rather than a single all-in-one cloud. A diagnosis request stays inside the EU from upload to response.&#xA;&#xA;Why not just use a US cloud&#39;s European region?&#xA;&#xA;Because an EU region of a US company is still governed by that company and, ultimately, by US legal reach over it. Using independent European providers keeps the data physically in the EU and out of any single foreign entity&#39;s control. It&#39;s more work to run, which is the trade I chose to make.&#xA;&#xA;Did the API change? Do I need to update anything?&#xA;&#xA;No. The endpoints, your API key, and the response format are unchanged. Inference still runs in milliseconds. The move was designed to be invisible to integrators - nothing in your code needs to change.&#xA;&#xA;Why does this matter for cannabis growers specifically?&#xA;&#xA;A plant photo can reveal that you grow, the kind of operation you run, and at scale, how large it is. For a licensed European operation that&#39;s commercially sensitive and sits inside a regulatory frame. Data stored in the EU under EU law, across providers no single foreign company controls, is a stronger answer to &#34;where does my grow data live&#34; than data in a US cloud.&#xA;&#xA;Does this make PlantLab GDPR or EU AI Act compliant?&#xA;&#xA;EU-based infrastructure supports those goals but isn&#39;t a certificate, and no honest small tool should claim one. PlantLab pairs EU hosting with bounded opt-in retention, encryption of sensitive fields at rest, and cookieless analytics - controls that move in the same direction the regulation does.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;A Plant Photo Says More Than You Think: Privacy by Design at PlantLab - What we keep, for how long, and why&#xA;How PlantLab Knows When It Might Be Wrong: The reliability_score Field - The trust signal on every diagnosis&#xA;What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide - The grower-facing diagnostic hub&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/d2tKz53d.png" alt="Map of Europe rendered in PlantLab&#39;s dark interface palette with bright markers over Germany, Slovenia, and France, representing distributed EU plant diagnosis infrastructure"/></p>

<p>Building on American cloud is the easy choice, which is exactly why almost everyone makes it. One account at Amazon, Google, or Microsoft and you get the whole stack in one place: compute, database, storage, DNS, email, all of it wired together, billed on one invoice, documented to death. It is a genuinely excellent product. It is also where PlantLab started.</p>

<p>It doesn&#39;t run there anymore. The diagnosis API, the database that holds your history, and the services around them now run on European infrastructure, and the live path a request travels – upload, inference, response, storage – never leaves the EU. That was not the easy choice. I want to explain why I think the easy choice and the right one were two different things here.</p>



<hr/>

<h2 id="the-all-in-one-pitch-is-real-and-it-s-the-trap" id="the-all-in-one-pitch-is-real-and-it-s-the-trap">The all-in-one pitch is real, and it&#39;s the trap</h2>

<p>The reason a hyperscaler is so easy to build on is that it has already solved the hard part for you: integration. You don&#39;t think about how your database talks to your storage, or whether your email provider and your DNS provider have ever heard of each other. One vendor owns all of it, so it all just fits. For a solo founder with limited hours, that convenience is worth a lot.</p>

<p>The catch is what you hand over in exchange. Your data, and the rules that govern it, now live inside one American company&#39;s estate, under one country&#39;s legal reach, no matter which “region” you tick in the console. An EU region of a US cloud is still a US cloud. The convenience and the loss of sovereignty are the same decision – you can&#39;t take one without the other.</p>

<p>For a lot of software that trade is fine. For a tool that gets handed photos from real, often licensed, grow rooms, I didn&#39;t think it was.</p>

<hr/>

<h2 id="europe-is-fractured-and-that-s-the-whole-point" id="europe-is-fractured-and-that-s-the-whole-point">Europe is fractured, and that&#39;s the whole point</h2>

<p>Here is the honest part nobody puts in the brochure: there is no European hyperscaler. There is no single EU console where you tick a box and get a sovereign, all-in-one stack. So you build it the hard way – you assemble it.</p>

<p>You host compute with one provider in one country. You find a separate DNS and content-delivery company in another. You put your database and transactional email with providers in a third. And before you trust any of them you do the unglamorous research: is this company actually European-owned and European-hosted, or is it a US firm with an EU postcode? Where does the data physically sit? Who can be compelled to hand it over, and under whose law? More vendors, more contracts, more reading, more things that can break.</p>

<p>That fragmentation is real, and it is the cost of sovereignty. The single invoice is convenient precisely because one entity controls everything. The moment you insist that no single entity should, you inherit the work of stitching independent pieces together yourself. I think that work is the point, not a bug to be engineered away.</p>

<hr/>

<h2 id="what-the-assembling-actually-produced" id="what-the-assembling-actually-produced">What the assembling actually produced</h2>

<p>Here is where things stand now. Three countries, several independent European providers, deliberately not one console:</p>

<table>
<thead>
<tr>
<th>Layer</th>
<th>Where it runs now</th>
</tr>
</thead>

<tbody>
<tr>
<td>Diagnosis API (the inference itself)</td>
<td>Germany</td>
</tr>

<tr>
<td>Database (your history, account, keys)</td>
<td>France</td>
</tr>

<tr>
<td>Transactional email</td>
<td>France</td>
</tr>

<tr>
<td>DNS and content delivery</td>
<td>Slovenia</td>
</tr>

<tr>
<td>Web analytics</td>
<td>EU-hosted, cookieless</td>
</tr>

<tr>
<td>Uptime monitoring</td>
<td>France</td>
</tr>
</tbody>
</table>

<p>The cutover was the careful kind. I ran the new European stack alongside the old one, sent real traffic through it, and verified end to end that a diagnosis written on the new infrastructure could be read back correctly, including the parts that are encrypted at rest. Only then did production point at it. The old environment is still sitting there, frozen, as a rollback target for a while longer, because turning the lights off the same day you cut over is how you turn a migration into an incident.</p>

<p>In an earlier post on data privacy I said this move was in progress and was careful not to overclaim it – the core diagnosis API still ran on a US cloud at the time. That caveat is gone now. The whole live path is European.</p>

<hr/>

<h2 id="why-the-hard-road" id="why-the-hard-road">Why the hard road</h2>

<p>I didn&#39;t do this for a marketing line, and I&#39;m wary of anyone who treats “EU-hosted” as a badge. I did it because of what a plant photo actually is.</p>

<p>A photo of a flowering plant gives things away – that you grow, the kind of setup you run, and across enough images, the scale of it. For a licensed European operation that is commercially sensitive information sitting inside a regulatory frame. The question that operator asks before sending anything real is simple: where does my data live, and whose rules govern it? “On servers in Germany, under EU law, with no single foreign company holding the whole stack” is a different answer than “somewhere in a US cloud&#39;s European region.”</p>

<p>There is a regulatory tailwind too. Europe&#39;s high-risk AI obligations come into force in August 2026, and the broader direction on privacy keeps moving toward stronger consent and more transparency, not less. Building here now, while PlantLab is small and the change is cheap, beats retrofitting it under a deadline later. But the regulation is the tailwind, not the reason. None of this makes PlantLab a compliance product, and you should distrust any small tool that claims a certificate.</p>

<p>The reason is plainer than that. Data sovereignty, privacy, and digital rights belong to the person whose data it is – not to whichever cloud happens to be cheapest to build on. Most companies build on US infrastructure because it&#39;s easy and it works, and I understand why. I took the harder, more fragmented road because, for a tool handling this kind of data, the user is the one who matters most. The convenience was mine to give up. The data was never mine to be casual with.</p>

<hr/>

<h2 id="what-this-changes-for-you" id="what-this-changes-for-you">What this changes for you</h2>

<p>For most people using the API, the answer is: nothing you have to do, which is the point. The endpoints are the same, your API key is the same, the response format is the same. Inference still runs in milliseconds – the model didn&#39;t change, only the building it runs in. A migration you have to think about is a migration done badly.</p>

<p>What it changes is what&#39;s true underneath:</p>
<ul><li><strong>Your diagnosis data is processed and stored in the EU.</strong> The live request path stays inside European infrastructure from upload to response, across providers that no single foreign entity controls.</li>
<li><strong>Sovereignty is distributed on purpose.</strong> No one company holds your data, your DNS, and your delivery layer at once. That&#39;s harder to run and harder to compromise wholesale.</li>
<li><strong>The privacy controls travel with it.</strong> Bounded, opt-in retention and encryption of the sensitive diagnosis fields ride on top of the move. Where your data lives and how it&#39;s held now point the same way.</li></ul>

<p>If you opt in to contributing diagnoses on the free tier, those images are kept in EU storage as well. The default is still minimization: opt-in, bounded, then deleted.</p>

<hr/>

<h2 id="stated-accurately-not-stretched" id="stated-accurately-not-stretched">Stated accurately, not stretched</h2>

<p>“EU-based” is a phrase that gets stretched until it means a billing address. I&#39;d rather it mean something concrete. Here it does: the request that carries your plant photo is served from Germany, your history is stored in the EU, traffic is measured by cookieless EU analytics, and uptime is watched by EU monitoring – each from an independent European provider. The live path your data travels, in real time, is European end to end. That&#39;s the claim, and it&#39;s a specific one.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, results in milliseconds. The full API documentation, including data handling details, lives at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>Where does PlantLab run now?</strong></p>

<p>The diagnosis API is served from Germany, the database and transactional email are in France, and DNS and content delivery run from Slovenia. Analytics are cookieless and EU-hosted, and uptime monitoring runs from France – each from an independent European provider rather than a single all-in-one cloud. A diagnosis request stays inside the EU from upload to response.</p>

<p><strong>Why not just use a US cloud&#39;s European region?</strong></p>

<p>Because an EU region of a US company is still governed by that company and, ultimately, by US legal reach over it. Using independent European providers keeps the data physically in the EU and out of any single foreign entity&#39;s control. It&#39;s more work to run, which is the trade I chose to make.</p>

<p><strong>Did the API change? Do I need to update anything?</strong></p>

<p>No. The endpoints, your API key, and the response format are unchanged. Inference still runs in milliseconds. The move was designed to be invisible to integrators – nothing in your code needs to change.</p>

<p><strong>Why does this matter for cannabis growers specifically?</strong></p>

<p>A plant photo can reveal that you grow, the kind of operation you run, and at scale, how large it is. For a licensed European operation that&#39;s commercially sensitive and sits inside a regulatory frame. Data stored in the EU under EU law, across providers no single foreign company controls, is a stronger answer to “where does my grow data live” than data in a US cloud.</p>

<p><strong>Does this make PlantLab GDPR or EU AI Act compliant?</strong></p>

<p>EU-based infrastructure supports those goals but isn&#39;t a certificate, and no honest small tool should claim one. PlantLab pairs EU hosting with bounded opt-in retention, encryption of sensitive fields at rest, and cookieless analytics – controls that move in the same direction the regulation does.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/plant-diagnosis-data-privacy">A Plant Photo Says More Than You Think: Privacy by Design at PlantLab</a> – What we keep, for how long, and why
– <a href="https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score">How PlantLab Knows When It Might Be Wrong: The reliability_score Field</a> – The trust signal on every diagnosis
– <a href="https://blog.plantlab.ai/whats-wrong-with-my-cannabis-plant">What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide</a> – The grower-facing diagnostic hub</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/plantlab-runs-in-europe</guid>
      <pubDate>Sat, 06 Jun 2026 14:27:33 +0000</pubDate>
    </item>
    <item>
      <title>What Growers Actually Need From AI: Field Notes After WCCBE Frankfurt</title>
      <link>https://blog.plantlab.ai/what-growers-need-from-ai-wccbe?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[World Class Cannabis Business Europe (WCCBE) 2026 logo&#xA;&#xA;The Short Version&#xA;&#xA;I spent two days in May chairing and speaking at World Class Cannabis Business Europe (WCCBE) in Frankfurt, on the subject of visual AI for cannabis cultivation. The biggest takeaway wasn&#39;t about the technology. It was about the gap between what the AI industry likes to talk about - autonomy, end-to-end automation, &#34;AI runs the grow&#34; - and what growers in the room actually asked for, which was something much more grounded. They want a tool that helps them decide, not one that decides for them. They want it to be specifically good at plants, not generically good at everything. Here&#39;s what I heard, and what it means for where PlantLab goes next.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;The setting, and an honest mismatch&#xA;&#xA;WCCBE Frankfurt, May 19-20, 2026. I chaired a day and gave a talk on visual AI for cannabis - the kind of plant-by-plant diagnosis problem PlantLab works on.&#xA;&#xA;The first honest thing to say is that a deep cultivation-diagnosis talk is not the same animal as a policy, regulation, and investment talk, and a conference that spans all of those is going to have rooms with very different centers of gravity. The business and policy sessions are genuinely useful for credibility and for understanding where the industry is heading. But the product conversation - the one where someone tells you what they&#39;d actually use on a Tuesday in their grow room - happens with operators, not in the M&amp;A track. That&#39;s not a criticism of the event. It&#39;s a note to myself about which rooms the product story belongs in.&#xA;&#xA;With that said, the technical lessons that came out of the operator conversations were sharp enough to be worth writing down.&#xA;&#xA;---&#xA;&#xA;Lesson 1: Growers want a co-pilot before they want an autopilot&#xA;&#xA;The pitch the AI industry loves is autonomy: the system watches, decides, and acts, and the human steps back. Almost nobody I talked to wanted that as the first step.&#xA;&#xA;What they wanted was a tool that makes their own judgment faster and more confident. Flag the plant that looks off. Tell me what you think it is and how sure you are. Then let me decide. The grower stays in the loop because the grower is accountable for the crop, and because they have context the camera doesn&#39;t - what they fed it last week, what the room did overnight, what this strain always does at this stage.&#xA;&#xA;This maps cleanly onto a design choice: the most valuable output isn&#39;t a decision, it&#39;s a well-calibrated suggestion plus an honest signal of how much to trust it. Autonomy can come later, on the narrow slices where the tool has earned it. Human-in-the-loop is not a stepping stone to be skipped. For most growers it&#39;s the actual product.&#xA;&#xA;---&#xA;&#xA;Lesson 2: Generic AI keeps disappointing growers&#xA;&#xA;This came up again and again, usually as a half-frustrated aside: people have tried asking a general-purpose AI assistant what&#39;s wrong with their plant, and it gives them an answer that sounds authoritative and is often wrong, with no signal that it might be wrong.&#xA;&#xA;This is the wedge, and it&#39;s a fair one to talk about publicly because it&#39;s about outcomes, not methods. A general model trained on the whole internet knows a little about cannabis among a billion other things. It will confidently call magnesium deficiency on something that&#39;s actually a pH problem, because the visual distinctions between plant health issues are subtle and the general model never had to get them right. Worse, it presents every answer with the same smooth confidence, so the grower has no way to tell the good answers from the bad ones.&#xA;&#xA;A purpose-built tool earns its place precisely here: it&#39;s narrow on purpose, it&#39;s measured on how well it does the specific thing, and it can tell you when it&#39;s unsure. &#34;Specifically good and honest about its limits&#34; beats &#34;generally capable and uniformly confident&#34; for anyone making a real cultivation decision.&#xA;&#xA;---&#xA;&#xA;Lesson 3: The near-term home for visual diagnosis is controlled indoor cultivation&#xA;&#xA;Visual AI diagnosis works best where the inputs are consistent: stable lighting, repeatable camera angles, a known set of plants, a controlled environment. That description is controlled indoor cultivation, and it&#39;s also where the people most willing to adopt new tooling tend to be.&#xA;&#xA;Outdoor and greenhouse settings introduce variability - weather, mixed lighting, scale - that makes consistent visual diagnosis harder. None of that is unsolvable, but it&#39;s the second problem, not the first. The honest near-term answer is that indoor, controlled grows are where this technology delivers reliable value today, and that&#39;s where the product should aim first.&#xA;&#xA;---&#xA;&#xA;Lesson 4: The automation companies need plant-state signals, not another app&#xA;&#xA;A recurring theme in the hardware and automation conversations: the companies building controllers, sensors, and grow-room automation don&#39;t need another consumer-facing diagnosis app. They need a plant-state signal they can pull into what they already build.&#xA;&#xA;That&#39;s a different shape of product. It says the diagnosis capability should be available as something an automation system can consume - a clean signal of plant condition and how trustworthy it is - rather than only as an app a human opens. The growers running those rooms have already chosen their controllers and their dashboards. The useful move is to feed plant-state into those systems, not to ask everyone to adopt yet another screen.&#xA;&#xA;---&#xA;&#xA;Where this points PlantLab&#xA;&#xA;Four lessons, one direction. Take the product to the rooms where operators are, not only the rooms where the industry talks about itself. Keep the diagnosis specifically good and honest about its uncertainty, because that&#39;s the thing generic AI can&#39;t do. Aim first at controlled indoor cultivation, where visual diagnosis is reliable today. And make plant-state available to the automation systems growers already run, instead of competing for their attention with another standalone app.&#xA;&#xA;None of these are surprising in hindsight. That&#39;s usually the sign of a good conference - it doesn&#39;t hand you a new idea so much as it sharpens the ones you arrived with and tells you which were wishful thinking. The wishful one was autonomy-first. The sharpened one was that being narrowly excellent and honest about it is the whole game.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, results in milliseconds. If you build grow-room automation and want a plant-state signal to integrate, the API documentation lives at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;What is WCCBE?&#xA;&#xA;World Class Cannabis Business Europe (WCCBE), held in Frankfurt in May 2026, covering cultivation, policy, regulation, and business across the European cannabis sector. I chaired a day and spoke on visual AI for cannabis cultivation.&#xA;&#xA;Why not just use ChatGPT to diagnose my plants?&#xA;&#xA;A general-purpose AI knows a little about cannabis among everything else, and presents wrong answers with the same confidence as right ones. A purpose-built diagnosis tool is narrow on purpose, measured on how well it does the specific task, and can signal when it&#39;s unsure - which is exactly what a general model can&#39;t do.&#xA;&#xA;Does PlantLab automate my grow room?&#xA;&#xA;PlantLab provides the diagnosis and a trust signal; what you do with it is your call. Most growers want a tool that informs their decision rather than one that acts for them, and the API is built to support that human-in-the-loop use first. It can also feed plant-state into automation systems for the cases where automatic action makes sense.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide - The grower-facing diagnostic hub&#xA;Confidence Is Not Reliability: Trust Signals for Automated Plant Diagnosis - How to know when to trust an automated answer&#xA;Build an Autonomous Plant Health Monitor with AI + Home Assistant - Feeding plant-state into automation&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/JU1lR4Rn.png" alt="World Class Cannabis Business Europe (WCCBE) 2026 logo"/></p>

<h2 id="the-short-version" id="the-short-version">The Short Version</h2>

<p>I spent two days in May chairing and speaking at World Class Cannabis Business Europe (WCCBE) in Frankfurt, on the subject of visual AI for cannabis cultivation. The biggest takeaway wasn&#39;t about the technology. It was about the gap between what the AI industry likes to talk about – autonomy, end-to-end automation, “AI runs the grow” – and what growers in the room actually asked for, which was something much more grounded. They want a tool that helps them decide, not one that decides for them. They want it to be specifically good at plants, not generically good at everything. Here&#39;s what I heard, and what it means for where PlantLab goes next.</p>



<hr/>

<h2 id="the-setting-and-an-honest-mismatch" id="the-setting-and-an-honest-mismatch">The setting, and an honest mismatch</h2>

<p>WCCBE Frankfurt, May 19-20, 2026. I chaired a day and gave a talk on visual AI for cannabis – the kind of plant-by-plant diagnosis problem PlantLab works on.</p>

<p>The first honest thing to say is that a deep cultivation-diagnosis talk is not the same animal as a policy, regulation, and investment talk, and a conference that spans all of those is going to have rooms with very different centers of gravity. The business and policy sessions are genuinely useful for credibility and for understanding where the industry is heading. But the product conversation – the one where someone tells you what they&#39;d actually use on a Tuesday in their grow room – happens with operators, not in the M&amp;A track. That&#39;s not a criticism of the event. It&#39;s a note to myself about which rooms the product story belongs in.</p>

<p>With that said, the technical lessons that came out of the operator conversations were sharp enough to be worth writing down.</p>

<hr/>

<h2 id="lesson-1-growers-want-a-co-pilot-before-they-want-an-autopilot" id="lesson-1-growers-want-a-co-pilot-before-they-want-an-autopilot">Lesson 1: Growers want a co-pilot before they want an autopilot</h2>

<p>The pitch the AI industry loves is autonomy: the system watches, decides, and acts, and the human steps back. Almost nobody I talked to wanted that as the first step.</p>

<p>What they wanted was a tool that makes their own judgment faster and more confident. Flag the plant that looks off. Tell me what you think it is and how sure you are. Then let me decide. The grower stays in the loop because the grower is accountable for the crop, and because they have context the camera doesn&#39;t – what they fed it last week, what the room did overnight, what this strain always does at this stage.</p>

<p>This maps cleanly onto a design choice: the most valuable output isn&#39;t a decision, it&#39;s a well-calibrated suggestion plus an honest signal of how much to trust it. Autonomy can come later, on the narrow slices where the tool has earned it. Human-in-the-loop is not a stepping stone to be skipped. For most growers it&#39;s the actual product.</p>

<hr/>

<h2 id="lesson-2-generic-ai-keeps-disappointing-growers" id="lesson-2-generic-ai-keeps-disappointing-growers">Lesson 2: Generic AI keeps disappointing growers</h2>

<p>This came up again and again, usually as a half-frustrated aside: people have tried asking a general-purpose AI assistant what&#39;s wrong with their plant, and it gives them an answer that sounds authoritative and is often wrong, with no signal that it might be wrong.</p>

<p>This is the wedge, and it&#39;s a fair one to talk about publicly because it&#39;s about outcomes, not methods. A general model trained on the whole internet knows a little about cannabis among a billion other things. It will confidently call magnesium deficiency on something that&#39;s actually a pH problem, because the visual distinctions between plant health issues are subtle and the general model never had to get them right. Worse, it presents every answer with the same smooth confidence, so the grower has no way to tell the good answers from the bad ones.</p>

<p>A purpose-built tool earns its place precisely here: it&#39;s narrow on purpose, it&#39;s measured on how well it does the specific thing, and it can tell you when it&#39;s unsure. “Specifically good and honest about its limits” beats “generally capable and uniformly confident” for anyone making a real cultivation decision.</p>

<hr/>

<h2 id="lesson-3-the-near-term-home-for-visual-diagnosis-is-controlled-indoor-cultivation" id="lesson-3-the-near-term-home-for-visual-diagnosis-is-controlled-indoor-cultivation">Lesson 3: The near-term home for visual diagnosis is controlled indoor cultivation</h2>

<p>Visual AI diagnosis works best where the inputs are consistent: stable lighting, repeatable camera angles, a known set of plants, a controlled environment. That description is controlled indoor cultivation, and it&#39;s also where the people most willing to adopt new tooling tend to be.</p>

<p>Outdoor and greenhouse settings introduce variability – weather, mixed lighting, scale – that makes consistent visual diagnosis harder. None of that is unsolvable, but it&#39;s the second problem, not the first. The honest near-term answer is that indoor, controlled grows are where this technology delivers reliable value today, and that&#39;s where the product should aim first.</p>

<hr/>

<h2 id="lesson-4-the-automation-companies-need-plant-state-signals-not-another-app" id="lesson-4-the-automation-companies-need-plant-state-signals-not-another-app">Lesson 4: The automation companies need plant-state signals, not another app</h2>

<p>A recurring theme in the hardware and automation conversations: the companies building controllers, sensors, and grow-room automation don&#39;t need another consumer-facing diagnosis app. They need a plant-state signal they can pull into what they already build.</p>

<p>That&#39;s a different shape of product. It says the diagnosis capability should be available as something an automation system can consume – a clean signal of plant condition and how trustworthy it is – rather than only as an app a human opens. The growers running those rooms have already chosen their controllers and their dashboards. The useful move is to feed plant-state into those systems, not to ask everyone to adopt yet another screen.</p>

<hr/>

<h2 id="where-this-points-plantlab" id="where-this-points-plantlab">Where this points PlantLab</h2>

<p>Four lessons, one direction. Take the product to the rooms where operators are, not only the rooms where the industry talks about itself. Keep the diagnosis specifically good and honest about its uncertainty, because that&#39;s the thing generic AI can&#39;t do. Aim first at controlled indoor cultivation, where visual diagnosis is reliable today. And make plant-state available to the automation systems growers already run, instead of competing for their attention with another standalone app.</p>

<p>None of these are surprising in hindsight. That&#39;s usually the sign of a good conference – it doesn&#39;t hand you a new idea so much as it sharpens the ones you arrived with and tells you which were wishful thinking. The wishful one was autonomy-first. The sharpened one was that being narrowly excellent and honest about it is the whole game.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, results in milliseconds. If you build grow-room automation and want a plant-state signal to integrate, the API documentation lives at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>What is WCCBE?</strong></p>

<p>World Class Cannabis Business Europe (WCCBE), held in Frankfurt in May 2026, covering cultivation, policy, regulation, and business across the European cannabis sector. I chaired a day and spoke on visual AI for cannabis cultivation.</p>

<p><strong>Why not just use ChatGPT to diagnose my plants?</strong></p>

<p>A general-purpose AI knows a little about cannabis among everything else, and presents wrong answers with the same confidence as right ones. A purpose-built diagnosis tool is narrow on purpose, measured on how well it does the specific task, and can signal when it&#39;s unsure – which is exactly what a general model can&#39;t do.</p>

<p><strong>Does PlantLab automate my grow room?</strong></p>

<p>PlantLab provides the diagnosis and a trust signal; what you do with it is your call. Most growers want a tool that informs their decision rather than one that acts for them, and the API is built to support that human-in-the-loop use first. It can also feed plant-state into automation systems for the cases where automatic action makes sense.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/whats-wrong-with-my-cannabis-plant">What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide</a> – The grower-facing diagnostic hub
– <a href="https://blog.plantlab.ai/confidence-is-not-reliability">Confidence Is Not Reliability: Trust Signals for Automated Plant Diagnosis</a> – How to know when to trust an automated answer
– <a href="https://blog.plantlab.ai/home-assistant-plant-monitoring">Build an Autonomous Plant Health Monitor with AI + Home Assistant</a> – Feeding plant-state into automation</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/what-growers-need-from-ai-wccbe</guid>
      <pubDate>Tue, 02 Jun 2026 14:21:24 +0000</pubDate>
    </item>
    <item>
      <title>A Plant Photo Says More Than You Think: Privacy by Design at PlantLab</title>
      <link>https://blog.plantlab.ai/plant-diagnosis-data-privacy?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Healthy cannabis plant thriving inside a grow facility with the surrounding equipment anonymized for privacy&#xA;&#xA;The Short Version&#xA;&#xA;When you send a plant photo to a diagnosis API, you are not just sending a picture of a leaf. You are sending a signal about what you grow, roughly where, and sometimes at what scale. PlantLab treats that as sensitive data. Diagnosis history is kept only if you opt in, only for a bounded window, and the sensitive parts are encrypted at rest. Analytics are cookieless, the supporting infrastructure is moving toward EU providers, and your API key is shown once and never emailed back to you in full. None of this is glamorous. All of it is the difference between an API you can hand real grow-room data and one you can&#39;t.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;A leaf photo is more revealing than it looks&#xA;&#xA;A single diagnosis request looks harmless: an image, a response, done in milliseconds. But the picture itself gives things away. A flowering cannabis plant in frame implies cultivation. A steady stream of them implies an operation. Add metadata, timing, and volume and you can start to estimate scale. For a hobby grower that&#39;s a privacy preference. For a licensed facility it&#39;s commercially sensitive - and in much of the world, cannabis cultivation sits inside a regulatory frame where careless data handling is a liability, not just bad manners.&#xA;&#xA;The honest way to think about this is to assume the data matters before anyone proves it does. The cost of treating a plant photo as sensitive is small. The cost of treating it as disposable and being wrong is not.&#xA;&#xA;That assumption is the whole design principle: keep what is genuinely useful, keep it for a bounded period, and make raw database access far less useful to anyone who shouldn&#39;t have it.&#xA;&#xA;---&#xA;&#xA;What we keep, and for how long&#xA;&#xA;PlantLab can show you your past diagnoses through the diagnosis history endpoint. That feature is useful - it lets you track a plant over time and gives integrators a stable record to reference. But I treat history as a choice you make, not a default I impose on you.&#xA;&#xA;Retention is opt-in. Storing your diagnosis history, and using images for model improvement, happens against a clear consent disclosure, not silently.&#xA;Retention is bounded by tier. History is kept for a defined window - 90 days on Pro, 365 days on Business - and then cleaned up automatically on a nightly schedule. The window is a published number, not an open-ended &#34;until we feel like deleting it.&#34;&#xA;Deletion is the default end state. When the window passes, the record goes. There is no quiet long tail of old data accumulating because nobody wrote the cleanup job.&#xA;&#xA;The principle here is data minimization by calendar. The most private record is the one that no longer exists, and the cheapest way to guarantee that is to delete on a schedule rather than on request.&#xA;&#xA;---&#xA;&#xA;Encryption at rest, stated accurately&#xA;&#xA;Sensitive diagnosis fields are encrypted at rest. If someone were to obtain a raw copy of the database, the sensitive columns would not be readable as plain text.&#xA;&#xA;I want to be precise about what that claim is and isn&#39;t, because &#34;encrypted&#34; is a word I&#39;ve watched get stretched until it means nothing. PlantLab encrypts the sensitive diagnosis fields - not a hand-wavy &#34;the whole database is encrypted, trust us.&#34; The design uses standard, portable PostgreSQL encryption rather than a proprietary scheme, so it can be audited, reasoned about, and moved between environments without leaning on one vendor&#39;s black box. The point is narrow and real: it raises the cost of a database compromise from &#34;read everything&#34; to &#34;read very little.&#34; One layer, described as one layer.&#xA;&#xA;---&#xA;&#xA;Your API key is yours, shown once&#xA;&#xA;A practical piece of the same posture: PlantLab no longer emails raw API keys. When you create a key, you see it once in the interface, with an acknowledgement step and a rotate button. The follow-up email contains only a safe prefix so you can identify which key it refers to - never the full secret.&#xA;&#xA;This matters because email is a long-lived, widely-synced, frequently-breached store. A secret that lands in an inbox lives in that inbox, on every device synced to it, in every backup of it, indefinitely. Showing a key once in the UI and never transmitting it in full keeps the most sensitive credential out of the least private channel. Sensitive account actions are also recorded in a structured way, so there is an audit trail for the things that should have one.&#xA;&#xA;---&#xA;&#xA;Cookieless analytics and a move toward EU infrastructure&#xA;&#xA;Two more changes are visible if you look closely at how the site behaves.&#xA;&#xA;The analytics are cookieless. I replaced Google Analytics with a privacy-native setup that sets no advertising cookie, which is why you won&#39;t see a cookie wall on the site. It counts aggregate traffic, not individual visitors followed around the web.&#xA;&#xA;The infrastructure is also moving toward EU providers. Over the last few months I shifted content delivery and DNS off a US-centric stack onto an EU-based CDN, and moved transactional email to a provider in France. Analytics are EU-hosted too. This is a migration in progress, not a finished state - the core diagnosis API still runs on major cloud infrastructure today - and it would be dishonest to claim the whole stack has relocated. The honest version: I&#39;m deliberately moving supporting services toward EU-friendly providers, and that work is still going.&#xA;&#xA;That direction is not an accident of taste. Data-protection expectations are tightening, not loosening. The EU&#39;s high-risk AI obligations come into force in August 2026, and broader privacy regulation keeps moving toward stronger consent, retention discipline, and transparency about automated decisions. Building the quiet controls now - bounded retention, encryption, cookieless measurement, EU-leaning infrastructure - is cheaper than retrofitting them under a deadline. None of this makes PlantLab a compliance product, and you should be suspicious of any small tool that claims a regulatory certification. It makes PlantLab an API that is moving in the same direction the rules are.&#xA;&#xA;---&#xA;&#xA;Why bother, when nobody asks&#xA;&#xA;Privacy work is invisible by design. No grower opens an app and thinks, &#34;I appreciate that the diagnosis history is deleted on a 90-day schedule and the sensitive columns are encrypted.&#34; The feature you notice is the diagnosis. The privacy work only becomes visible the day something goes wrong, and by then it&#39;s too late to add it.&#xA;&#xA;The reason to do it anyway is that an automation API gets handed real data from real grow rooms. The more useful PlantLab becomes - feeding dashboards, triggering Home Assistant automations, logging plant state over a full grow cycle - the more that data accumulates and the more it matters how it&#39;s held. The boring controls are what make the useful version safe enough to actually use.&#xA;&#xA;That&#39;s the trade. Privacy work is quiet, it doesn&#39;t demo well, and it&#39;s the part of building a plant health API that has to be right before any of the interesting parts are worth trusting.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, results in milliseconds. The full API documentation, including data handling details, lives at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;Does PlantLab store my plant photos?&#xA;&#xA;Storing diagnosis history and using images for model improvement is opt-in, disclosed through a consent step rather than enabled silently. If you opt in, history is kept for a bounded window per tier (90 days on Pro, 365 days on Business) and then deleted automatically. The default posture is minimization - keep what&#39;s useful, for a defined period, then remove it.&#xA;&#xA;What does &#34;encrypted at rest&#34; actually mean here?&#xA;&#xA;The sensitive diagnosis fields are stored encrypted in the database using standard, portable PostgreSQL encryption. If someone obtained a raw copy of the database, those fields would not be readable as plain text. It&#39;s a specific control on specific fields, not a blanket &#34;the whole system is encrypted&#34; claim.&#xA;&#xA;Is my API key safe?&#xA;&#xA;Your key is shown once in the interface when you create it, with a rotate option. PlantLab does not email raw keys - the email contains only a safe prefix so you can identify the key. The full secret stays out of your inbox.&#xA;&#xA;Is PlantLab EU-based?&#xA;&#xA;PlantLab is deliberately moving supporting services - CDN, DNS, email, analytics - toward EU providers, and analytics are cookieless and EU-hosted. This is a migration in progress; the core diagnosis API still runs on major cloud infrastructure. I&#39;d rather describe it accurately than overclaim a finished relocation.&#xA;&#xA;Why does plant diagnosis data need privacy at all?&#xA;&#xA;Because a cannabis plant photo gives things away - that you&#39;re growing, the kind of setup you run, and across many photos, the scale of it. For a licensed operation that&#39;s commercially sensitive and often regulated. Treating it as sensitive by default costs little; treating it as disposable and being wrong costs a lot.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;How PlantLab Knows When It Might Be Wrong: The reliability_score Field - The trust signal on every diagnosis&#xA;How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds - The pipeline behind the API&#xA;What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide - The grower-facing diagnostic hub&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/ygs5s4LH.png" alt="Healthy cannabis plant thriving inside a grow facility with the surrounding equipment anonymized for privacy"/></p>

<h2 id="the-short-version" id="the-short-version">The Short Version</h2>

<p>When you send a plant photo to a diagnosis API, you are not just sending a picture of a leaf. You are sending a signal about what you grow, roughly where, and sometimes at what scale. PlantLab treats that as sensitive data. Diagnosis history is kept only if you opt in, only for a bounded window, and the sensitive parts are encrypted at rest. Analytics are cookieless, the supporting infrastructure is moving toward EU providers, and your API key is shown once and never emailed back to you in full. None of this is glamorous. All of it is the difference between an API you can hand real grow-room data and one you can&#39;t.</p>



<hr/>

<h2 id="a-leaf-photo-is-more-revealing-than-it-looks" id="a-leaf-photo-is-more-revealing-than-it-looks">A leaf photo is more revealing than it looks</h2>

<p>A single diagnosis request looks harmless: an image, a response, done in milliseconds. But the picture itself gives things away. A flowering cannabis plant in frame implies cultivation. A steady stream of them implies an operation. Add metadata, timing, and volume and you can start to estimate scale. For a hobby grower that&#39;s a privacy preference. For a licensed facility it&#39;s commercially sensitive – and in much of the world, cannabis cultivation sits inside a regulatory frame where careless data handling is a liability, not just bad manners.</p>

<p>The honest way to think about this is to assume the data matters before anyone proves it does. The cost of treating a plant photo as sensitive is small. The cost of treating it as disposable and being wrong is not.</p>

<p>That assumption is the whole design principle: <strong>keep what is genuinely useful, keep it for a bounded period, and make raw database access far less useful to anyone who shouldn&#39;t have it.</strong></p>

<hr/>

<h2 id="what-we-keep-and-for-how-long" id="what-we-keep-and-for-how-long">What we keep, and for how long</h2>

<p>PlantLab can show you your past diagnoses through the diagnosis history endpoint. That feature is useful – it lets you track a plant over time and gives integrators a stable record to reference. But I treat history as a choice you make, not a default I impose on you.</p>
<ul><li><strong>Retention is opt-in.</strong> Storing your diagnosis history, and using images for model improvement, happens against a clear consent disclosure, not silently.</li>
<li><strong>Retention is bounded by tier.</strong> History is kept for a defined window – 90 days on Pro, 365 days on Business – and then cleaned up automatically on a nightly schedule. The window is a published number, not an open-ended “until we feel like deleting it.”</li>
<li><strong>Deletion is the default end state.</strong> When the window passes, the record goes. There is no quiet long tail of old data accumulating because nobody wrote the cleanup job.</li></ul>

<p>The principle here is data minimization by calendar. The most private record is the one that no longer exists, and the cheapest way to guarantee that is to delete on a schedule rather than on request.</p>

<hr/>

<h2 id="encryption-at-rest-stated-accurately" id="encryption-at-rest-stated-accurately">Encryption at rest, stated accurately</h2>

<p>Sensitive diagnosis fields are encrypted at rest. If someone were to obtain a raw copy of the database, the sensitive columns would not be readable as plain text.</p>

<p>I want to be precise about what that claim is and isn&#39;t, because “encrypted” is a word I&#39;ve watched get stretched until it means nothing. PlantLab encrypts the sensitive diagnosis fields – not a hand-wavy “the whole database is encrypted, trust us.” The design uses standard, portable PostgreSQL encryption rather than a proprietary scheme, so it can be audited, reasoned about, and moved between environments without leaning on one vendor&#39;s black box. The point is narrow and real: it raises the cost of a database compromise from “read everything” to “read very little.” One layer, described as one layer.</p>

<hr/>

<h2 id="your-api-key-is-yours-shown-once" id="your-api-key-is-yours-shown-once">Your API key is yours, shown once</h2>

<p>A practical piece of the same posture: PlantLab no longer emails raw API keys. When you create a key, you see it once in the interface, with an acknowledgement step and a rotate button. The follow-up email contains only a safe prefix so you can identify which key it refers to – never the full secret.</p>

<p>This matters because email is a long-lived, widely-synced, frequently-breached store. A secret that lands in an inbox lives in that inbox, on every device synced to it, in every backup of it, indefinitely. Showing a key once in the UI and never transmitting it in full keeps the most sensitive credential out of the least private channel. Sensitive account actions are also recorded in a structured way, so there is an audit trail for the things that should have one.</p>

<hr/>

<h2 id="cookieless-analytics-and-a-move-toward-eu-infrastructure" id="cookieless-analytics-and-a-move-toward-eu-infrastructure">Cookieless analytics and a move toward EU infrastructure</h2>

<p>Two more changes are visible if you look closely at how the site behaves.</p>

<p>The analytics are cookieless. I replaced Google Analytics with a privacy-native setup that sets no advertising cookie, which is why you won&#39;t see a cookie wall on the site. It counts aggregate traffic, not individual visitors followed around the web.</p>

<p>The infrastructure is also moving toward EU providers. Over the last few months I shifted content delivery and DNS off a US-centric stack onto an EU-based CDN, and moved transactional email to a provider in France. Analytics are EU-hosted too. This is a migration in progress, not a finished state – the core diagnosis API still runs on major cloud infrastructure today – and it would be dishonest to claim the whole stack has relocated. The honest version: I&#39;m deliberately moving supporting services toward EU-friendly providers, and that work is still going.</p>

<p>That direction is not an accident of taste. Data-protection expectations are tightening, not loosening. The EU&#39;s high-risk AI obligations come into force in August 2026, and broader privacy regulation keeps moving toward stronger consent, retention discipline, and transparency about automated decisions. Building the quiet controls now – bounded retention, encryption, cookieless measurement, EU-leaning infrastructure – is cheaper than retrofitting them under a deadline. None of this makes PlantLab a compliance product, and you should be suspicious of any small tool that claims a regulatory certification. It makes PlantLab an API that is moving in the same direction the rules are.</p>

<hr/>

<h2 id="why-bother-when-nobody-asks" id="why-bother-when-nobody-asks">Why bother, when nobody asks</h2>

<p>Privacy work is invisible by design. No grower opens an app and thinks, “I appreciate that the diagnosis history is deleted on a 90-day schedule and the sensitive columns are encrypted.” The feature you notice is the diagnosis. The privacy work only becomes visible the day something goes wrong, and by then it&#39;s too late to add it.</p>

<p>The reason to do it anyway is that an automation API gets handed real data from real grow rooms. The more useful PlantLab becomes – feeding dashboards, triggering Home Assistant automations, logging plant state over a full grow cycle – the more that data accumulates and the more it matters how it&#39;s held. The boring controls are what make the useful version safe enough to actually use.</p>

<p>That&#39;s the trade. Privacy work is quiet, it doesn&#39;t demo well, and it&#39;s the part of building a plant health API that has to be right before any of the interesting parts are worth trusting.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, results in milliseconds. The full API documentation, including data handling details, lives at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>Does PlantLab store my plant photos?</strong></p>

<p>Storing diagnosis history and using images for model improvement is opt-in, disclosed through a consent step rather than enabled silently. If you opt in, history is kept for a bounded window per tier (90 days on Pro, 365 days on Business) and then deleted automatically. The default posture is minimization – keep what&#39;s useful, for a defined period, then remove it.</p>

<p><strong>What does “encrypted at rest” actually mean here?</strong></p>

<p>The sensitive diagnosis fields are stored encrypted in the database using standard, portable PostgreSQL encryption. If someone obtained a raw copy of the database, those fields would not be readable as plain text. It&#39;s a specific control on specific fields, not a blanket “the whole system is encrypted” claim.</p>

<p><strong>Is my API key safe?</strong></p>

<p>Your key is shown once in the interface when you create it, with a rotate option. PlantLab does not email raw keys – the email contains only a safe prefix so you can identify the key. The full secret stays out of your inbox.</p>

<p><strong>Is PlantLab EU-based?</strong></p>

<p>PlantLab is deliberately moving supporting services – CDN, DNS, email, analytics – toward EU providers, and analytics are cookieless and EU-hosted. This is a migration in progress; the core diagnosis API still runs on major cloud infrastructure. I&#39;d rather describe it accurately than overclaim a finished relocation.</p>

<p><strong>Why does plant diagnosis data need privacy at all?</strong></p>

<p>Because a cannabis plant photo gives things away – that you&#39;re growing, the kind of setup you run, and across many photos, the scale of it. For a licensed operation that&#39;s commercially sensitive and often regulated. Treating it as sensitive by default costs little; treating it as disposable and being wrong costs a lot.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score">How PlantLab Knows When It Might Be Wrong: The reliability_score Field</a> – The trust signal on every diagnosis
– <a href="https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds">How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds</a> – The pipeline behind the API
– <a href="https://blog.plantlab.ai/whats-wrong-with-my-cannabis-plant">What&#39;s Wrong With My Cannabis Plant? A Visual Diagnosis Guide</a> – The grower-facing diagnostic hub</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/plant-diagnosis-data-privacy</guid>
      <pubDate>Sun, 31 May 2026 13:26:14 +0000</pubDate>
    </item>
    <item>
      <title>Why a Plant Diagnosis Should Have History</title>
      <link>https://blog.plantlab.ai/plant-diagnosis-history?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[A row of green leaf markers along a thin timeline, with one larger and glowing brighter than the rest, suggesting a pattern recognized over time&#xA;&#xA;A single diagnosis tells you what&#39;s wrong now. A history of them tells you whether you&#39;re getting better.&#xA;&#xA;That&#39;s the gap PlantLab&#39;s new /history endpoint closes.&#xA;&#xA;!--more--&#xA;&#xA;When you call /diagnose with a photo, you get back a single answer about a single plant at a single moment. Useful, and also limited. You can&#39;t tell whether the answer you got today is consistent with the answer you got last week. You can&#39;t tell whether the intervention you ran four days ago made things better or worse. You can&#39;t see a pattern across photos because the API forgets each call the moment it returns.&#xA;&#xA;That&#39;s the gap /history closes.&#xA;&#xA;What changed&#xA;&#xA;Pro and Business accounts now have an opt-in setting that, when turned on, retains diagnosis history. The photo, the answer, the timestamp, the engine version, all queryable through a new /history endpoint. Pro keeps the last 90 days. Business keeps the last 365. Off means nothing is stored. The default is off.&#xA;&#xA;There&#39;s also a Home Assistant sensor exposed for the count if you want it on a dashboard. The endpoint supports cursor-based pagination, which matters more than it sounds once you&#39;ve been growing for a few months and your history grows past a screen.&#xA;&#xA;What you can do with it today&#xA;&#xA;Three things, on day one.&#xA;&#xA;You can see trajectory. Whether a plant is improving or sliding. The leaf that read nitrogen-deficient last Sunday: is it still showing the same signal this Sunday, or has it moved? A single diagnosis can&#39;t tell you.&#xA;&#xA;You can spot patterns across plants. If three different plants in the same tent keep coming back as overwatering, the watering schedule is the variable, not the plants. The room is the constant.&#xA;&#xA;You can audit the API itself. Whether the model is consistent shot-to-shot on the same plant under the same conditions. Two photos two minutes apart, same lighting, same angle, should produce broadly the same answer. /history makes that an inspectable claim instead of a vibe.&#xA;&#xA;What I want to build on top of it&#xA;&#xA;The endpoint is the substrate, not the analysis. The reason it has the shape it does, opt-in, retention-bounded, paginated, machine-readable, is that I want certain things to be possible later. None of them exist today. I&#39;m being deliberate about not promising them on a date.&#xA;&#xA;A simple &#34;this plant has been declining for X days&#34; flag, computed from the history rather than the latest photo alone.&#xA;&#xA;Intervention tagging. Mark a diagnosis with &#34;I tried foliar Cal-Mag&#34; and see whether the next photo improves. That requires a feedback loop the current API doesn&#39;t have. /history is one half of it.&#xA;&#xA;Anomaly detection that respects time. A plant that&#39;s been healthy for six weeks suddenly returning a confident pest signal is a different event from a plant that&#39;s been borderline for six weeks. Without history the API has no way to tell those two apart.&#xA;&#xA;All of those features need the data to exist in queryable form before they can be built. That&#39;s what /history is for.&#xA;&#xA;The privacy posture&#xA;&#xA;A stateless diagnosis API is genuinely stateless. You send a photo, you get an answer, the photo is gone. That posture is impossible the moment you start retaining anything, so the bar moves: if you&#39;re going to store, you store as little as possible, you make it opt-in, you delete on a known schedule, and you never share with a second party.&#xA;&#xA;That&#39;s what /history does. Off by default. The toggle is in your dashboard and on the API. Tier-bounded retention. Rolling deletion.&#xA;&#xA;It also slots into a wider set of changes this month that all point in the same direction. Analytics moved to Umami, EU-hosted, cookieless, no consent banner. The CDN moved to Bunny.net in Slovenia. The signup CAPTCHA moved to self-hosted Altcha. Each one of those moves cuts a third party out of the data path between you and PlantLab. Together they shrink the surface area of who sees what.&#xA;&#xA;The point of all of that is straightforward. Your grow data is yours. The job of an API like this isn&#39;t to accumulate it, it&#39;s to give you an answer and stay out of the way.&#xA;&#xA;How to use it&#xA;&#xA;If you&#39;re on Pro or Business, the toggle is in your dashboard under Account settings. The API exposes the same setting. Once it&#39;s on, /history returns your diagnoses in reverse chronological order, paginated, with the same field shape as /diagnose plus a timestamp and the engine version.&#xA;&#xA;Full docs at plantlab.ai/docs.&#xA;&#xA;If you build automations and you&#39;ve been waiting for a way to compare today&#39;s reading against last week&#39;s, this is the piece that was missing.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/iNLJmxby.png" alt="A row of green leaf markers along a thin timeline, with one larger and glowing brighter than the rest, suggesting a pattern recognized over time"/></p>

<p>A single diagnosis tells you what&#39;s wrong now. A history of them tells you whether you&#39;re getting better.</p>

<p>That&#39;s the gap PlantLab&#39;s new /history endpoint closes.</p>



<p>When you call /diagnose with a photo, you get back a single answer about a single plant at a single moment. Useful, and also limited. You can&#39;t tell whether the answer you got today is consistent with the answer you got last week. You can&#39;t tell whether the intervention you ran four days ago made things better or worse. You can&#39;t see a pattern across photos because the API forgets each call the moment it returns.</p>

<p>That&#39;s the gap /history closes.</p>

<h2 id="what-changed" id="what-changed">What changed</h2>

<p>Pro and Business accounts now have an opt-in setting that, when turned on, retains diagnosis history. The photo, the answer, the timestamp, the engine version, all queryable through a new /history endpoint. Pro keeps the last 90 days. Business keeps the last 365. Off means nothing is stored. The default is off.</p>

<p>There&#39;s also a Home Assistant sensor exposed for the count if you want it on a dashboard. The endpoint supports cursor-based pagination, which matters more than it sounds once you&#39;ve been growing for a few months and your history grows past a screen.</p>

<h2 id="what-you-can-do-with-it-today" id="what-you-can-do-with-it-today">What you can do with it today</h2>

<p>Three things, on day one.</p>

<p>You can see trajectory. Whether a plant is improving or sliding. The leaf that read nitrogen-deficient last Sunday: is it still showing the same signal this Sunday, or has it moved? A single diagnosis can&#39;t tell you.</p>

<p>You can spot patterns across plants. If three different plants in the same tent keep coming back as overwatering, the watering schedule is the variable, not the plants. The room is the constant.</p>

<p>You can audit the API itself. Whether the model is consistent shot-to-shot on the same plant under the same conditions. Two photos two minutes apart, same lighting, same angle, should produce broadly the same answer. /history makes that an inspectable claim instead of a vibe.</p>

<h2 id="what-i-want-to-build-on-top-of-it" id="what-i-want-to-build-on-top-of-it">What I want to build on top of it</h2>

<p>The endpoint is the substrate, not the analysis. The reason it has the shape it does, opt-in, retention-bounded, paginated, machine-readable, is that I want certain things to be possible later. None of them exist today. I&#39;m being deliberate about not promising them on a date.</p>

<p>A simple “this plant has been declining for X days” flag, computed from the history rather than the latest photo alone.</p>

<p>Intervention tagging. Mark a diagnosis with “I tried foliar Cal-Mag” and see whether the next photo improves. That requires a feedback loop the current API doesn&#39;t have. /history is one half of it.</p>

<p>Anomaly detection that respects time. A plant that&#39;s been healthy for six weeks suddenly returning a confident pest signal is a different event from a plant that&#39;s been borderline for six weeks. Without history the API has no way to tell those two apart.</p>

<p>All of those features need the data to exist in queryable form before they can be built. That&#39;s what /history is for.</p>

<h2 id="the-privacy-posture" id="the-privacy-posture">The privacy posture</h2>

<p>A stateless diagnosis API is genuinely stateless. You send a photo, you get an answer, the photo is gone. That posture is impossible the moment you start retaining anything, so the bar moves: if you&#39;re going to store, you store as little as possible, you make it opt-in, you delete on a known schedule, and you never share with a second party.</p>

<p>That&#39;s what /history does. Off by default. The toggle is in your dashboard and on the API. Tier-bounded retention. Rolling deletion.</p>

<p>It also slots into a wider set of changes this month that all point in the same direction. Analytics moved to Umami, EU-hosted, cookieless, no consent banner. The CDN moved to Bunny.net in Slovenia. The signup CAPTCHA moved to self-hosted Altcha. Each one of those moves cuts a third party out of the data path between you and PlantLab. Together they shrink the surface area of who sees what.</p>

<p>The point of all of that is straightforward. Your grow data is yours. The job of an API like this isn&#39;t to accumulate it, it&#39;s to give you an answer and stay out of the way.</p>

<h2 id="how-to-use-it" id="how-to-use-it">How to use it</h2>

<p>If you&#39;re on Pro or Business, the toggle is in your dashboard under Account settings. The API exposes the same setting. Once it&#39;s on, /history returns your diagnoses in reverse chronological order, paginated, with the same field shape as /diagnose plus a timestamp and the engine version.</p>

<p>Full docs at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</p>

<p>If you build automations and you&#39;ve been waiting for a way to compare today&#39;s reading against last week&#39;s, this is the piece that was missing.</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/plant-diagnosis-history</guid>
      <pubDate>Sun, 10 May 2026 17:43:53 +0000</pubDate>
    </item>
    <item>
      <title>How PlantLab Knows When It Might Be Wrong: The reliability_score Field</title>
      <link>https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[The Short Version&#xA;&#xA;PlantLab&#39;s API now returns a reliabilityscore field on every diagnosis. A number from 0 to 1 telling you how likely the answer is to be correct on this specific image. It replaces the old diagnosticconfidence and safetyclassification fields, which were rule-based guesses that I never trusted. The new score is much better at flagging the diagnoses that turn out to be wrong - especially on the hard cases, which is where you actually need it. Schema bumped from 1.x to 2.0.0. If you&#39;re integrating with PlantLab today, the migration is a one-line change.&#xA;&#xA;!--more--&#xA;&#xA;---&#xA;&#xA;The problem with &#34;confidence&#34; fields&#xA;&#xA;Most diagnosis APIs return a confidence number along with each answer. PlantLab did too. For every condition the model spotted, the response included a confidence value between 0 and 1. On top of that, the response also carried two derived fields. diagnosticconfidence, a single overall trust number, and safetyclassification, a three-way bucket of high, moderate, low.&#xA;&#xA;Those derived fields were a heuristic. A small handful of rules that mostly looked at the top condition&#39;s confidence and rolled it up into a number. Heuristics work fine when the problem is simple. They fall apart when the failure modes are subtle.&#xA;&#xA;In real traffic, the cases that matter are the ambiguous ones - photos where the answer isn&#39;t obvious from the image alone, and a single rule isn&#39;t enough to capture how confident the diagnosis really is. That&#39;s the slice where a trust signal earns its keep, and the slice where a rule-based composite tends to break.&#xA;&#xA;A trust signal that works on the easy cases and stops working on the harder ones isn&#39;t really a trust signal. It&#39;s a confidence display.&#xA;&#xA;---&#xA;&#xA;What reliabilityscore does differently&#xA;&#xA;reliabilityscore is a single number from 0 to 1 that estimates how likely the top diagnosis is to be correct on this specific image. Higher is better. Below 0.3 is a clear &#34;double-check this one.&#34; Above 0.7 is &#34;the system is confident and the confidence holds up.&#34;&#xA;&#xA;It doesn&#39;t replace per-class confidence. Those still tell you how strongly the model picked each individual condition. What reliabilityscore adds is a separate answer to a different question - &#34;is the entire diagnosis trustworthy on this particular image, or is something off?&#34;&#xA;&#xA;The analogy I keep coming back to: a junior diagnostician who always gives an answer, and a supervisor who looks over their shoulder. The supervisor doesn&#39;t redo the diagnosis. They judge whether each one looks trustworthy. The old diagnosticconfidence was a checklist the junior filled in themselves. reliabilityscore is the supervisor.&#xA;&#xA;I held the new score to a higher bar than the old composite. On the ambiguous cases, it does a much better job of flagging the answers you should double-check before acting on them. On the easy cases, both fields agree - which is the only place they were ever going to agree, and not where the score earns its keep.&#xA;&#xA;---&#xA;&#xA;What changes in the response&#xA;&#xA;If you&#39;re integrating with PlantLab today, here&#39;s what your code currently sees:&#xA;&#xA;{&#xA;  &#34;requestid&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,&#xA;  &#34;schemaversion&#34;: &#34;1.2.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;ishealthy&#34;: false,&#xA;  &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;  &#34;conditions&#34;: [&#xA;    { &#34;classid&#34;: &#34;magnesiumdeficiency&#34;, &#34;confidence&#34;: 0.85 }&#xA;  ],&#xA;  &#34;diagnosticconfidence&#34;: 0.85,&#xA;  &#34;safetyclassification&#34;: &#34;highconfidence&#34;&#xA;}&#xA;&#xA;After the upgrade, that same image returns:&#xA;&#xA;{&#xA;  &#34;requestid&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,&#xA;  &#34;schemaversion&#34;: &#34;2.0.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;ishealthy&#34;: false,&#xA;  &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;  &#34;conditions&#34;: [&#xA;    { &#34;classid&#34;: &#34;magnesiumdeficiency&#34;, &#34;confidence&#34;: 0.85 }&#xA;  ],&#xA;  &#34;reliabilityscore&#34;: 0.91&#xA;}&#xA;&#xA;Two fields removed. One field added. The rest of the response is identical.&#xA;&#xA;reliabilityscore is omitted when the API doesn&#39;t return a condition diagnosis - for example, when the photo isn&#39;t of cannabis, or when the plant is healthy. In those cases, there&#39;s no diagnosis to score for reliability, so the field doesn&#39;t appear. Treat its absence as &#34;no score available&#34; rather than &#34;low score.&#34;&#xA;&#xA;---&#xA;&#xA;Migration&#xA;&#xA;The change you make depends on what you were doing with the old fields.&#xA;&#xA;If you were displaying diagnosticconfidence to a user, swap to reliabilityscore. The semantics are the same direction (higher is better, both 0-1), and the new value is more accurate.&#xA;&#xA;If you were branching on safetyclassification strings, pick thresholds on reliabilityscore instead. A reasonable starting point: above 0.7 is &#34;Confident,&#34; 0.3 to 0.7 is &#34;Uncertain,&#34; below 0.3 is &#34;Low confidence.&#34; Your application can use whatever cutpoints make sense - the score is a number, not a string, so you have full flexibility.&#xA;&#xA;If you were ignoring the old fields entirely, the upgrade is automatic. Remove your code that references diagnosticconfidence or safetyclassification (it&#39;ll get null going forward) and you&#39;re done.&#xA;&#xA;The Home Assistant integration shipped a new release the same day as the API change, so existing HA users get the new sensor automatically. If you&#39;re using a custom integration, update it before the next API deploy if you can - sensors that read the removed fields will return null until the integration is updated.&#xA;&#xA;---&#xA;&#xA;Why a breaking schema, not deprecation&#xA;&#xA;I considered keeping diagnosticconfidence and safetyclassification as deprecated fields, returning the old values alongside the new score for a release or two. It would have spared everyone a migration step.&#xA;&#xA;But it forces consumers to choose between two trust signals that can disagree. The old composite says &#34;low confidence&#34; on a photo where the new score says 0.95 - which do you trust? Worse, deprecated fields stick around for months, and integrators keep reading them instead of migrating. That&#39;s basically the entire failure mode of deprecation.&#xA;&#xA;Cleaner break, single migration, no ambiguity. Schema bumped to 2.0.0 to make it loud. If your integration was on schema 1.x, you&#39;ll start getting 2.0.0 responses the next time you call the API. Field changes are documented above.&#xA;&#xA;---&#xA;&#xA;What&#39;s next&#xA;&#xA;reliabilityscore ships as v1. The field semantics stay stable: a 0 to 1 trust score, present on diagnoses that returned a condition prediction. Future improvements land behind that contract. Same field, more accurate values, no code changes on your end.&#xA;&#xA;If you migrate now, you&#39;re done with the migration.&#xA;&#xA;---&#xA;&#xA;PlantLab is free to try at plantlab.ai. Three diagnoses a day, results in milliseconds. The full API documentation, including the OpenAPI spec, lives at plantlab.ai/docs.&#xA;&#xA;---&#xA;&#xA;FAQ&#xA;&#xA;Do I have to migrate immediately?&#xA;&#xA;You&#39;ll start receiving schema 2.0.0 responses the next time you call the API. If your code reads diagnosticconfidence or safetyclassification, those reads will return null. If your code branches on those fields, your branches will fall through to whatever default path you wrote. So the migration urgency depends on what your code does with null values - some integrations will degrade gracefully, others will break.&#xA;&#xA;Is reliabilityscore the same as confidence?&#xA;&#xA;No. confidence (still present in conditions[] and pests[]) is the model&#39;s per-class probability for one specific class - &#34;how confident am I that this leaf shows magnesium deficiency?&#34; reliabilityscore is a separate signal that estimates how likely the entire diagnosis is to be correct on this image. The two answer different questions, and you can use both.&#xA;&#xA;What does it mean when reliabilityscore is missing?&#xA;&#xA;The score is only computed when the API returns a condition diagnosis - that is, when the photo is cannabis and the plant is unhealthy. For non-cannabis photos or healthy plants, there&#39;s no condition prediction to score, so the field is omitted. Treat absence as &#34;no score available,&#34; not as a low score.&#xA;&#xA;How is this different from just thresholding on confidence?&#xA;&#xA;Per-class confidence values are the model&#39;s individual outputs. They tell you which classes were predicted strongly. They don&#39;t tell you whether the diagnosis as a whole holds up on a given image. reliabilityscore answers that broader question, which is usually the one you actually have.&#xA;&#xA;Can I see PlantLab&#39;s diagnosis history for my key?&#xA;&#xA;GET /usage returns daily and monthly counts. For per-request lookup, store request_id from each diagnose response - it&#39;s stable, returned in both the JSON body and the X-Request-ID header. Use it for support tickets and feedback submission.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;The Work Nobody Sees: How I Ran 47 Experiments to Make PlantLab&#39;s AI Better - What goes into making the model more accurate, cycle by cycle&#xA;Yellow Leaves, Seven Suspects: How PlantLab Got Specific About Nutrient Deficiencies - The nutrient subclassifier that ships alongside this trust signal&#xA;How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds - The full pipeline&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="the-short-version" id="the-short-version">The Short Version</h2>

<p>PlantLab&#39;s API now returns a <code>reliability_score</code> field on every diagnosis. A number from 0 to 1 telling you how likely the answer is to be correct on this specific image. It replaces the old <code>diagnostic_confidence</code> and <code>safety_classification</code> fields, which were rule-based guesses that I never trusted. The new score is much better at flagging the diagnoses that turn out to be wrong – especially on the hard cases, which is where you actually need it. Schema bumped from 1.x to 2.0.0. If you&#39;re integrating with PlantLab today, the migration is a one-line change.</p>



<hr/>

<h2 id="the-problem-with-confidence-fields" id="the-problem-with-confidence-fields">The problem with “confidence” fields</h2>

<p>Most diagnosis APIs return a confidence number along with each answer. PlantLab did too. For every condition the model spotted, the response included a <code>confidence</code> value between 0 and 1. On top of that, the response also carried two derived fields. <code>diagnostic_confidence</code>, a single overall trust number, and <code>safety_classification</code>, a three-way bucket of high, moderate, low.</p>

<p>Those derived fields were a heuristic. A small handful of rules that mostly looked at the top condition&#39;s confidence and rolled it up into a number. Heuristics work fine when the problem is simple. They fall apart when the failure modes are subtle.</p>

<p>In real traffic, the cases that matter are the ambiguous ones – photos where the answer isn&#39;t obvious from the image alone, and a single rule isn&#39;t enough to capture how confident the diagnosis really is. That&#39;s the slice where a trust signal earns its keep, and the slice where a rule-based composite tends to break.</p>

<p>A trust signal that works on the easy cases and stops working on the harder ones isn&#39;t really a trust signal. It&#39;s a confidence display.</p>

<hr/>

<h2 id="what-reliability-score-does-differently" id="what-reliability-score-does-differently">What reliability_score does differently</h2>

<p><code>reliability_score</code> is a single number from 0 to 1 that estimates how likely the top diagnosis is to be correct on this specific image. Higher is better. Below 0.3 is a clear “double-check this one.” Above 0.7 is “the system is confident and the confidence holds up.”</p>

<p>It doesn&#39;t replace per-class <code>confidence</code>. Those still tell you how strongly the model picked each individual condition. What <code>reliability_score</code> adds is a separate answer to a different question – “is the entire diagnosis trustworthy on this particular image, or is something off?”</p>

<p>The analogy I keep coming back to: a junior diagnostician who always gives an answer, and a supervisor who looks over their shoulder. The supervisor doesn&#39;t redo the diagnosis. They judge whether each one looks trustworthy. The old <code>diagnostic_confidence</code> was a checklist the junior filled in themselves. <code>reliability_score</code> is the supervisor.</p>

<p>I held the new score to a higher bar than the old composite. On the ambiguous cases, it does a much better job of flagging the answers you should double-check before acting on them. On the easy cases, both fields agree – which is the only place they were ever going to agree, and not where the score earns its keep.</p>

<hr/>

<h2 id="what-changes-in-the-response" id="what-changes-in-the-response">What changes in the response</h2>

<p>If you&#39;re integrating with PlantLab today, here&#39;s what your code currently sees:</p>

<pre><code class="language-json">{
  &#34;request_id&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,
  &#34;schema_version&#34;: &#34;1.2.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;is_healthy&#34;: false,
  &#34;growth_stage&#34;: &#34;flowering&#34;,
  &#34;conditions&#34;: [
    { &#34;class_id&#34;: &#34;magnesium_deficiency&#34;, &#34;confidence&#34;: 0.85 }
  ],
  &#34;diagnostic_confidence&#34;: 0.85,
  &#34;safety_classification&#34;: &#34;high_confidence&#34;
}
</code></pre>

<p>After the upgrade, that same image returns:</p>

<pre><code class="language-json">{
  &#34;request_id&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,
  &#34;schema_version&#34;: &#34;2.0.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;is_healthy&#34;: false,
  &#34;growth_stage&#34;: &#34;flowering&#34;,
  &#34;conditions&#34;: [
    { &#34;class_id&#34;: &#34;magnesium_deficiency&#34;, &#34;confidence&#34;: 0.85 }
  ],
  &#34;reliability_score&#34;: 0.91
}
</code></pre>

<p>Two fields removed. One field added. The rest of the response is identical.</p>

<p><code>reliability_score</code> is omitted when the API doesn&#39;t return a condition diagnosis – for example, when the photo isn&#39;t of cannabis, or when the plant is healthy. In those cases, there&#39;s no diagnosis to score for reliability, so the field doesn&#39;t appear. Treat its absence as “no score available” rather than “low score.”</p>

<hr/>

<h2 id="migration" id="migration">Migration</h2>

<p>The change you make depends on what you were doing with the old fields.</p>

<p>If you were displaying <code>diagnostic_confidence</code> to a user, swap to <code>reliability_score</code>. The semantics are the same direction (higher is better, both 0-1), and the new value is more accurate.</p>

<p>If you were branching on <code>safety_classification</code> strings, pick thresholds on <code>reliability_score</code> instead. A reasonable starting point: above 0.7 is “Confident,” 0.3 to 0.7 is “Uncertain,” below 0.3 is “Low confidence.” Your application can use whatever cutpoints make sense – the score is a number, not a string, so you have full flexibility.</p>

<p>If you were ignoring the old fields entirely, the upgrade is automatic. Remove your code that references <code>diagnostic_confidence</code> or <code>safety_classification</code> (it&#39;ll get null going forward) and you&#39;re done.</p>

<p>The Home Assistant integration shipped a new release the same day as the API change, so existing HA users get the new sensor automatically. If you&#39;re using a custom integration, update it before the next API deploy if you can – sensors that read the removed fields will return null until the integration is updated.</p>

<hr/>

<h2 id="why-a-breaking-schema-not-deprecation" id="why-a-breaking-schema-not-deprecation">Why a breaking schema, not deprecation</h2>

<p>I considered keeping <code>diagnostic_confidence</code> and <code>safety_classification</code> as deprecated fields, returning the old values alongside the new score for a release or two. It would have spared everyone a migration step.</p>

<p>But it forces consumers to choose between two trust signals that can disagree. The old composite says “low confidence” on a photo where the new score says 0.95 – which do you trust? Worse, deprecated fields stick around for months, and integrators keep reading them instead of migrating. That&#39;s basically the entire failure mode of deprecation.</p>

<p>Cleaner break, single migration, no ambiguity. Schema bumped to 2.0.0 to make it loud. If your integration was on schema 1.x, you&#39;ll start getting 2.0.0 responses the next time you call the API. Field changes are documented above.</p>

<hr/>

<h2 id="what-s-next" id="what-s-next">What&#39;s next</h2>

<p><code>reliability_score</code> ships as v1. The field semantics stay stable: a 0 to 1 trust score, present on diagnoses that returned a condition prediction. Future improvements land behind that contract. Same field, more accurate values, no code changes on your end.</p>

<p>If you migrate now, you&#39;re done with the migration.</p>

<hr/>

<p><em>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. Three diagnoses a day, results in milliseconds. The full API documentation, including the OpenAPI spec, lives at <a href="https://plantlab.ai/docs">plantlab.ai/docs</a>.</em></p>

<hr/>

<h2 id="faq" id="faq">FAQ</h2>

<p><strong>Do I have to migrate immediately?</strong></p>

<p>You&#39;ll start receiving schema 2.0.0 responses the next time you call the API. If your code reads <code>diagnostic_confidence</code> or <code>safety_classification</code>, those reads will return null. If your code branches on those fields, your branches will fall through to whatever default path you wrote. So the migration urgency depends on what your code does with null values – some integrations will degrade gracefully, others will break.</p>

<p><strong>Is reliability_score the same as confidence?</strong></p>

<p>No. <code>confidence</code> (still present in <code>conditions[]</code> and <code>pests[]</code>) is the model&#39;s per-class probability for one specific class – “how confident am I that this leaf shows magnesium deficiency?” <code>reliability_score</code> is a separate signal that estimates how likely the entire diagnosis is to be correct on this image. The two answer different questions, and you can use both.</p>

<p><strong>What does it mean when reliability_score is missing?</strong></p>

<p>The score is only computed when the API returns a condition diagnosis – that is, when the photo is cannabis and the plant is unhealthy. For non-cannabis photos or healthy plants, there&#39;s no condition prediction to score, so the field is omitted. Treat absence as “no score available,” not as a low score.</p>

<p><strong>How is this different from just thresholding on <code>confidence</code>?</strong></p>

<p>Per-class <code>confidence</code> values are the model&#39;s individual outputs. They tell you which classes were predicted strongly. They don&#39;t tell you whether the diagnosis as a whole holds up on a given image. <code>reliability_score</code> answers that broader question, which is usually the one you actually have.</p>

<p><strong>Can I see PlantLab&#39;s diagnosis history for my key?</strong></p>

<p><code>GET /usage</code> returns daily and monthly counts. For per-request lookup, store <code>request_id</code> from each diagnose response – it&#39;s stable, returned in both the JSON body and the <code>X-Request-ID</code> header. Use it for support tickets and feedback submission.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/behind-the-model-continuous-improvement">The Work Nobody Sees: How I Ran 47 Experiments to Make PlantLab&#39;s AI Better</a> – What goes into making the model more accurate, cycle by cycle
– <a href="https://blog.plantlab.ai/yellow-leaves-seven-suspects-how-plantlab-got-specific-about-nutrient">Yellow Leaves, Seven Suspects: How PlantLab Got Specific About Nutrient Deficiencies</a> – The nutrient subclassifier that ships alongside this trust signal
– <a href="https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds">How PlantLab&#39;s AI Diagnoses 31 Cannabis Plant Problems in 18 Milliseconds</a> – The full pipeline</p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score</guid>
      <pubDate>Mon, 04 May 2026 16:14:08 +0000</pubDate>
    </item>
    <item>
      <title>How AI Diagnoses Cannabis Plant Diseases: PlantLab&#39;s 31-Condition Model</title>
      <link>https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[The short version&#xA;&#xA;Most plant diagnosis tools give you a paragraph to read. PlantLab gives your automation system something to act on.&#xA;&#xA;The model covers 31 cannabis conditions and pests at 99.1% balanced accuracy. Balanced means every class counts equally - a system that nails common deficiencies but misses rare pests does not score well. The output is structured JSON that Home Assistant, Node-RED, or a custom controller can read and act on without a person in the loop.&#xA;&#xA;!--more--&#xA;&#xA;Why generic AI fails&#xA;&#xA;The first time I tried AI for plant diagnosis, I uploaded a photo to ChatGPT. It told me I had a calcium deficiency. It was light burn. The two look nothing alike if you know what you are looking at, but ChatGPT was never trained specifically on plant images. It is a convincing generalist, and when it does not know, it guesses.&#xA;&#xA;That is what most &#34;AI plant diagnosis&#34; apps actually do. Wrap a general-purpose language model, send your photo with a prompt, return whatever comes back. The output is confident, plausible, and sometimes wrong, and a new grower has no easy way to tell which time is which. It is also something you can do yourself for free, which makes paying for the service hard to justify.&#xA;&#xA;The deeper problem is that even when these tools are right, they hand you prose. Useful for a person reading a screen. Useless for an automation system that needs to decide whether to adjust pH, run a fan, or send you an alert.&#xA;&#xA;---&#xA;&#xA;What PlantLab detects&#xA;&#xA;The model covers 31 cannabis conditions and pests across four families.&#xA;&#xA;Nutrient issues: nitrogen, phosphorus, potassium, calcium, magnesium, iron, boron, manganese, and zinc deficiencies, plus nitrogen toxicity.&#xA;&#xA;Diseases: powdery mildew, bud rot, root rot, pythium, rust fungi, septoria, and mosaic virus.&#xA;&#xA;Pests: spider mites, thrips, aphids, whiteflies, fungus gnats, caterpillars, leafhoppers, leaf miners, and mealybugs.&#xA;&#xA;Environmental: light burn, light deficiency, heat stress, overwatering, and underwatering.&#xA;&#xA;Every class scores above 95% detection accuracy, including the rarer ones.&#xA;&#xA;What you get back&#xA;&#xA;{&#xA;  &#34;requestid&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,&#xA;  &#34;schemaversion&#34;: &#34;2.0.0&#34;,&#xA;  &#34;success&#34;: true,&#xA;  &#34;iscannabis&#34;: true,&#xA;  &#34;ishealthy&#34;: false,&#xA;  &#34;growthstage&#34;: &#34;flowering&#34;,&#xA;  &#34;conditions&#34;: [&#xA;    { &#34;classid&#34;: &#34;budrot&#34;, &#34;confidence&#34;: 0.92 }&#xA;  ],&#xA;  &#34;pests&#34;: [],&#xA;  &#34;reliabilityscore&#34;: 0.88&#xA;}&#xA;&#xA;Not a paragraph for a person to read and interpret. A machine-readable signal. Your controller sees 92% confidence on bud rot in a flowering plant and can ramp airflow, send an alert, or log the event - keeping you informed without forcing you to step in every time.&#xA;&#xA;reliabilityscore is a separate trust signal on top of per-class confidence. It estimates whether the entire diagnosis holds up on this specific image, which is most useful on the hard cases - mixed symptoms, lookalike conditions, edge-case growth stages. There is more on it in How PlantLab Knows When It Might Be Wrong.&#xA;&#xA;---&#xA;&#xA;What&#39;s new in this release&#xA;&#xA;The previous version of the model covered 24 conditions. This release brings it to 31. The additions came from what growers actually run into and ask about.&#xA;&#xA;Bud rot is one of the worst things that can happen during flowering. Dense colas plus humid air invite Botrytis, and by the time you can see it with the naked eye, it has often already spread.&#xA;&#xA;Heat stress causes leaf curling, foxtailing, and bleaching that new growers often confuse with nutrient issues. Splitting it into its own class prevents the misdiagnosis.&#xA;&#xA;Fungus gnats are usually the first pest a new indoor grower meets. Caterpillars, leafhoppers, and leaf miners are common outdoor threats. Mealybugs are less common but brutal once they take hold. All five now have dedicated detection.&#xA;&#xA;Boron, manganese, and zinc deficiencies fill out the micronutrient coverage. Less common than the macros, but harder to spot by eye because their symptoms overlap with other conditions.&#xA;&#xA;---&#xA;&#xA;A diagnosis that surprised me&#xA;&#xA;I sent a sample of recent images through the live service to spot-check it against my own intuition.&#xA;&#xA;One result stood out. The photo was a plant that looked underwatered - drooping, leaves curling, the classic signs. The model called it overwatered. I was ready to write that off as wrong, then I went back through earlier photos. The plant had been chronically overwatered for weeks. That ongoing stress had caused nutrient lockout, which then progressed into something that looked like underwatering. The model caught the underlying cause. Without that, I would have treated the symptom and made the problem worse.&#xA;&#xA;---&#xA;&#xA;What&#39;s next&#xA;&#xA;A few things in the queue.&#xA;&#xA;Multiple concurrent conditions in one image. Plants can have spider mites and a calcium deficiency at the same time. Today the API returns the primary diagnosis. Multi-label output is on the way.&#xA;&#xA;Step-by-step automation guides. Home Assistant, Node-RED, and others - walkthroughs for wiring PlantLab into the stack you already run.&#xA;&#xA;More real-world data. Photos from real tents, at real angles, in real lighting, sharpen the model on the conditions it actually sees - not just the clean reference shots.&#xA;&#xA;PlantLab is free to try at plantlab.ai. The API returns structured JSON for every diagnosis - plug it into your automation stack and let your grow room see for itself.&#xA;&#xA;---&#xA;&#xA;Related reading:&#xA;Why I Built PlantLab - The origin story&#xA;How PlantLab Knows When It Might Be Wrong - The reliabilityscore field and schema 2.0&#xA;Nitrogen Deficiency in Cannabis: A Visual Guide - Detailed guide for the most common deficiency&#xA;Yellow Leaves, Seven Suspects - Specific nutrient identification&#xA;API Documentation&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="the-short-version" id="the-short-version">The short version</h2>

<p>Most plant diagnosis tools give you a paragraph to read. PlantLab gives your automation system something to act on.</p>

<p>The model covers 31 cannabis conditions and pests at 99.1% balanced accuracy. Balanced means every class counts equally – a system that nails common deficiencies but misses rare pests does not score well. The output is structured JSON that Home Assistant, Node-RED, or a custom controller can read and act on without a person in the loop.</p>



<h2 id="why-generic-ai-fails" id="why-generic-ai-fails">Why generic AI fails</h2>

<p>The first time I tried AI for plant diagnosis, I uploaded a photo to ChatGPT. It told me I had a calcium deficiency. It was light burn. The two look nothing alike if you know what you are looking at, but ChatGPT was never trained specifically on plant images. It is a convincing generalist, and when it does not know, it guesses.</p>

<p>That is what most “AI plant diagnosis” apps actually do. Wrap a general-purpose language model, send your photo with a prompt, return whatever comes back. The output is confident, plausible, and sometimes wrong, and a new grower has no easy way to tell which time is which. It is also something you can do yourself for free, which makes paying for the service hard to justify.</p>

<p>The deeper problem is that even when these tools are right, they hand you prose. Useful for a person reading a screen. Useless for an automation system that needs to decide whether to adjust pH, run a fan, or send you an alert.</p>

<hr/>

<h2 id="what-plantlab-detects" id="what-plantlab-detects">What PlantLab detects</h2>

<p>The model covers 31 cannabis conditions and pests across four families.</p>

<p>Nutrient issues: nitrogen, phosphorus, potassium, calcium, magnesium, iron, boron, manganese, and zinc deficiencies, plus nitrogen toxicity.</p>

<p>Diseases: powdery mildew, bud rot, root rot, pythium, rust fungi, septoria, and mosaic virus.</p>

<p>Pests: spider mites, thrips, aphids, whiteflies, fungus gnats, caterpillars, leafhoppers, leaf miners, and mealybugs.</p>

<p>Environmental: light burn, light deficiency, heat stress, overwatering, and underwatering.</p>

<p>Every class scores above 95% detection accuracy, including the rarer ones.</p>

<h3 id="what-you-get-back" id="what-you-get-back">What you get back</h3>

<pre><code class="language-json">{
  &#34;request_id&#34;: &#34;550e8400-e29b-41d4-a716-446655440000&#34;,
  &#34;schema_version&#34;: &#34;2.0.0&#34;,
  &#34;success&#34;: true,
  &#34;is_cannabis&#34;: true,
  &#34;is_healthy&#34;: false,
  &#34;growth_stage&#34;: &#34;flowering&#34;,
  &#34;conditions&#34;: [
    { &#34;class_id&#34;: &#34;bud_rot&#34;, &#34;confidence&#34;: 0.92 }
  ],
  &#34;pests&#34;: [],
  &#34;reliability_score&#34;: 0.88
}
</code></pre>

<p>Not a paragraph for a person to read and interpret. A machine-readable signal. Your controller sees 92% confidence on bud rot in a flowering plant and can ramp airflow, send an alert, or log the event – keeping you informed without forcing you to step in every time.</p>

<p><code>reliability_score</code> is a separate trust signal on top of per-class <code>confidence</code>. It estimates whether the entire diagnosis holds up on this specific image, which is most useful on the hard cases – mixed symptoms, lookalike conditions, edge-case growth stages. There is more on it in <a href="https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score">How PlantLab Knows When It Might Be Wrong</a>.</p>

<hr/>

<h2 id="what-s-new-in-this-release" id="what-s-new-in-this-release">What&#39;s new in this release</h2>

<p>The previous version of the model covered 24 conditions. This release brings it to 31. The additions came from what growers actually run into and ask about.</p>

<p>Bud rot is one of the worst things that can happen during flowering. Dense colas plus humid air invite Botrytis, and by the time you can see it with the naked eye, it has often already spread.</p>

<p>Heat stress causes leaf curling, foxtailing, and bleaching that new growers often confuse with nutrient issues. Splitting it into its own class prevents the misdiagnosis.</p>

<p>Fungus gnats are usually the first pest a new indoor grower meets. Caterpillars, leafhoppers, and leaf miners are common outdoor threats. Mealybugs are less common but brutal once they take hold. All five now have dedicated detection.</p>

<p>Boron, manganese, and zinc deficiencies fill out the micronutrient coverage. Less common than the macros, but harder to spot by eye because their symptoms overlap with other conditions.</p>

<hr/>

<h2 id="a-diagnosis-that-surprised-me" id="a-diagnosis-that-surprised-me">A diagnosis that surprised me</h2>

<p>I sent a sample of recent images through the live service to spot-check it against my own intuition.</p>

<p>One result stood out. The photo was a plant that looked underwatered – drooping, leaves curling, the classic signs. The model called it overwatered. I was ready to write that off as wrong, then I went back through earlier photos. The plant had been chronically overwatered for weeks. That ongoing stress had caused nutrient lockout, which then progressed into something that looked like underwatering. The model caught the underlying cause. Without that, I would have treated the symptom and made the problem worse.</p>

<hr/>

<h2 id="what-s-next" id="what-s-next">What&#39;s next</h2>

<p>A few things in the queue.</p>

<p>Multiple concurrent conditions in one image. Plants can have spider mites and a calcium deficiency at the same time. Today the API returns the primary diagnosis. Multi-label output is on the way.</p>

<p>Step-by-step automation guides. Home Assistant, Node-RED, and others – walkthroughs for wiring PlantLab into the stack you already run.</p>

<p>More real-world data. Photos from real tents, at real angles, in real lighting, sharpen the model on the conditions it actually sees – not just the clean reference shots.</p>

<p>PlantLab is free to try at <a href="https://plantlab.ai">plantlab.ai</a>. The API returns structured JSON for every diagnosis – plug it into your automation stack and let your grow room see for itself.</p>

<hr/>

<p><em>Related reading:</em>
– <a href="https://blog.plantlab.ai/why-i-built-plantlab">Why I Built PlantLab</a> – The origin story
– <a href="https://blog.plantlab.ai/how-plantlab-knows-when-it-might-be-wrong-reliability-score">How PlantLab Knows When It Might Be Wrong</a> – The reliability_score field and schema 2.0
– <a href="https://blog.plantlab.ai/nitrogen-deficiency-in-cannabis-a-visual-guide">Nitrogen Deficiency in Cannabis: A Visual Guide</a> – Detailed guide for the most common deficiency
– <a href="https://blog.plantlab.ai/yellow-leaves-seven-suspects-how-plantlab-got-specific-about-nutrient">Yellow Leaves, Seven Suspects</a> – Specific nutrient identification
– <a href="https://plantlab.ai/docs">API Documentation</a></p>
]]></content:encoded>
      <guid>https://blog.plantlab.ai/how-plantlabs-ai-diagnoses-31-cannabis-plant-problems-in-18-milliseconds</guid>
      <pubDate>Thu, 30 Apr 2026 14:11:16 +0000</pubDate>
    </item>
  </channel>
</rss>