Add pool condition detection #9

Open
opened 2026-07-28 15:37:15 +00:00 by eliribble · 4 comments
Owner

I want you to look at the current design of nemish and design out how we'll add pool condition detection to the daemon subcommand. Right now the system works by downloading a satellite image, calculating a pool mask, and then translating that mask into a geometry. All of this data is then sent back to nidus-sync. Nidus-sync recently developed the ability to track not just pool location, but pool condition:

  • Blue (maintained)
  • Dry
  • Green (unmaintained)
  • Murky (deep/can't see to the bottom)
  • Covered

In general we detect these by looking at the color of the pool. I'd like you to design two things. First, how would we change the training system of nemish to not just find pools but to learn to categorize them by their condition. Second, once we have that trained model, how would we update the daemon process to use the model to also output the pool condition?

I want you to look at the current design of nemish and design out how we'll add pool condition detection to the daemon subcommand. Right now the system works by downloading a satellite image, calculating a pool mask, and then translating that mask into a geometry. All of this data is then sent back to nidus-sync. Nidus-sync recently developed the ability to track not just pool location, but pool condition: * Blue (maintained) * Dry * Green (unmaintained) * Murky (deep/can't see to the bottom) * Covered In general we detect these by looking at the color of the pool. I'd like you to design two things. First, how would we change the training system of nemish to not just find pools but to learn to categorize them by their condition. Second, once we have that trained model, how would we update the daemon process to use the model to also output the pool condition?
Member

Pool Condition Detection in Nemish — Design Proposal

Context

Current nemish pipeline

Satellite tile → [binary segmentation model] → pool mask (bg=0, pool=1)
                                              → connected components (CV2)
                                              → polygon fitting (circular/rectangular/irregular)
                                              → WGS84 GeoJSON geometry
                                              → submit mask + geometry to nidus-sync

nidus-sync pool condition model

feature_pool_state tracks condition as an enum with values already defined:

Value Visual meaning
blue Maintained (clear blue water)
dry Empty / no water
green Unmaintained (algae)
murky Deep or turbid water, can't see bottom
covered Pool cover or solar blanket
false pool Not actually a pool (operational, not visual)
unknown Default when condition isn't determined

The detectable conditions by satellite imagery color analysis are: blue, dry, green, murky, covered. I'll design for these five + background.


Part 1: Training System Changes

Analysis of approaches

Option A: Multi-class semantic segmentation (pixel-level)

Change the model from binary (pool vs background) to N-class where each class = condition:

  • Class 0 = background
  • Class 1 = blue (maintained)
  • Class 2 = green (unmaintained)
  • Class 3 = dry
  • Class 4 = murky
  • Class 5 = covered

What changes in the code:

  1. Config (default.yaml): data.num_classes → 6 (or N+1)
  2. Training masks: need per-pixel condition labels (0-5 integer). The existing PoolDataset validates for binary 0/255 and exits on unexpected values — a new dataset variant or a mode flag is needed.
  3. Loss function: CombinedLoss(CrossEntropy + Dice) already works for multi-class. Likely adjust ce_weight/dice_weight since Dice can be noisy with many rare classes.
  4. Metrics (compute_iou, compute_dice): already handle num_classes > 2. Each class gets its own IoU/Dice report.
  5. Inference (predict_array): still works — returns per-pixel argmax over N classes.
  6. Post-processing: for each connected component in the pool mask, aggregate pixel-level condition predictions to get a single pool-level condition (majority vote, or max-confidence vote).

Challenges:

  • Requires existing training masks to be re-labeled at pixel granularity with condition. That's a big annotation lift.
  • A pool can legitimately have mixed color pixels (shadows, reflections, edge effects). Pixel-level noise needs smoothing.
  • The SMP UNet decoder channels grow from 2→6, which marginally increases parameter count but doesn't change architecture.
  • Rare conditions (e.g., covered) may be heavily class-imbalanced, requiring class weighting.

Stage 1 (unchanged): binary pool segmentation as today.
Stage 2: for each detected pool polygon, classify its condition.

Two viable sub-approaches for Stage 2:

B1 — Lightweight color-statistics classifier:

  • Extract per-pixel (RGB) values from the satellite tile within each pool mask
  • Compute features: mean(R,G,B), median, std dev, quantiles, HSV hue histogram
  • Classify with a small model (MLP with 2 hidden layers, < 50K params) or even sklearn RandomForest
  • Training data: patches of known pools with condition labels
  • Strengths: Very fast inference, no GPU needed for stage 2, easy to iterate
  • Weaknesses: May confuse murky vs blue in some lighting conditions

B2 — CNN patch classifier:

  • For each pool polygon, crop the bounding-box region from the satellite tile
  • Resize to fixed size (e.g., 64×64) and run through a small CNN classifier (ResNet-18, MobileNetV3)
  • Training data: cropped pool images with condition labels
  • Strengths: Captures spatial texture (algae patterns, cover wrinkles, shade gradients)
  • Weaknesses: Needs GPU for efficient inference on many pools; more data-hungry

Why two-stage is better than multi-class segmentation:

  1. Modularity — the pool detection pipeline doesn't change. Condition detection is a bolt-on.
  2. Data efficiency — condition training data can be pool-level labels (much cheaper than pixel-level masks). You can annotate condition at the pool/card level in the UI.
  3. Incremental rollout — deploy pool detection first (already working), add condition detection later. If condition models are wrong, pool maps are unaffected.
  4. Composability — you can use different techniques for different conditions (e.g., simple thresholding for "covered" which has very distinct visual signature, ML for subtler brown/green/murky decisions).

Option C: Multi-task segmentation (shared encoder, two decoders)

One shared encoder (ResNet-34) with two decoder heads:

  • Head 1: Binary pool mask (2 channels)
  • Head 2: Condition logits (N channels), masked by pool regions

This preserves the binary mask quality while learning condition features, but it's the most complex to implement, debug, and train. The codebase doesn't support multi-head architectures yet, and this design doc isn't the place for that scope.

Start with B1 (color-statistics MLP) as the MVP — it's the quickest path to a working condition classifier:

  • Features: per-pool-pixel RGB mean, median, std, plus HSV hue histogram (32 bins)
  • Classifier: 2-layer MLP (64→32→N) with ReLU + dropout
  • Training: pool-level labels only, generated from existing nidus-sync pool data where condition is known

As a fallback/Baseline for covered pools (which are very distinct — usually a dark rectangle/blue tarp), add a simple heuristic pre-classifier: if mean pixel brightness < threshold and edge density > threshold → "covered". This can be a 20-line function and catch most covered pools with near-perfect accuracy.

The MLP classifier training pipeline would be a new subcommand:

nemish train-condition \
  --pools /data/pool-conditions.csv   # pool_id, tile_ref, condition label
  --checkpoint /data/model/best.pth    # existing pool detection model (for mask extraction)

Part 2: Daemon Integration

What the daemon currently does (in _inference_worker)

1. predict_array(image) → (mask, pool_probs)
2. pool_mask = mask > 0
3. For each connected component of pool_mask:
   a. extract_polygons_from_mask() → polygons
   b. polygons_to_wgs84_geometry() → geometry GeoJSON
4. Submit mask PNG + confidence + geometry to nidus-sync

What needs to change for condition detection

1. predict_array(image) → (mask, pool_probs)   # unchanged
2. pool_mask = mask > 0                         # unchanged
3. For each connected component:
   a. extract_polygons_from_mask() → polygons   # unchanged
   b. polygons_to_wgs84_geometry() → geometry   # unchanged
   c. classify_pool_condition(image, pool_mask_component) → condition  # NEW
4. Submit mask PNG + confidence + geometry + condition to nidus-sync  # MODIFIED

Changes by file

src/daemon.py

  1. Load condition classifier during inference worker init (alongside the pool detection model):

    condition_model = load_condition_classifier(model_dir)  # small MLP or ONNX
    
  2. New function — classify_pool_condition():

    def classify_pool_condition(
        tile_image: np.ndarray,        # original RGB tile
        pool_mask: np.ndarray,         # binary mask for one pool
        classifier: Any,               # loaded condition classifier
        image_size: int = 256,
    ) -> tuple[str, float]:
        """Return (condition_label, confidence) for a single pool."""
        # Crop the pool bounding box from the tile
        ys, xs = np.where(pool_mask > 0)
        xmin, ymin, xmax, ymax = xs.min(), ys.min(), xs.max(), ys.max()
        pool_crop = tile_image[ymin:ymax, xmin:xmax]
        pool_mask_crop = pool_mask[ymin:ymax, xmin:xmax]
    
        # Extract per-pixel color features (masked within pool boundaries)
        features = extract_color_features(pool_crop, pool_mask_crop)
    
        # Run classifier
        return classifier.predict(features)  # -> ("blue", 0.92)
    
  3. Modified _submit_result() — also include condition in the payload:

    data = {
        "confidence": str(result["confidence"]),
        "conditions": json.dumps(result["conditions"]),  # List[pool_condition]
    }
    

    Where conditions is a list of per-pool conditions matching the order of polygons in the geometry.

src/inference/predict.py

Add a new condition classifier module (or a new file src/classify.py):

def load_condition_classifier(model_path: str, device: str = "cpu"):
    """Load the condition classification model."""
    ...

def extract_color_features(crop: np.ndarray, mask: np.ndarray) -> np.ndarray:
    """Extract per-pixel color statistics from the masked pool region.
    
    Returns a feature vector: [mean_r, mean_g, mean_b, std_r, std_g, std_b,
                                median_r, median_g, median_b, 
                                hue_hist_0..31]  (41 features)
    """
    ...

src/training/condition_classifier.py (new file)

Training pipeline for the condition MLP classifier:

class PoolConditionDataset(Dataset):
    """Pool-level condition labels with tile image references."""
    
class ConditionMLP(nn.Module):
    """Small multi-layer perceptron for condition classification."""

def train_condition_classifier(cfg):
    """Training loop for the condition classifier."""

nidus-sync side

The /api/vision/task/{id}/inference endpoint needs to accept a conditions field. Currently it reads confidence (form field) and mask (file). Adding:

conditionsStr := r.FormValue("conditions")  // JSON array of condition strings

The vision_analysis_task table already has confidence. We'd add a new migration to store per-task conditions. However, since a task (tile) may contain multiple pools with different conditions, the condition belongs on the geometry features or in a separate task_pool_conditions table.

Recommended approach: Store conditions as a property on the geometry GeoJSON. Each polygon Feature gets a condition property. The existing GeoJSON pipeline already attaches per-polygon properties (id, area_pixels, num_vertices). Just add condition.

The postVisionTaskInference handler already receives geometry in form data (optional from the daemon, though currently unused in that handler — geometry is separately PUT via /vision/task/{id}). We should:

  1. Add a conditions form field to the inference POST
  2. Store conditions alongside geometry properties when the analysis is later materialized into pool records (in the vision_analysis.go flow where tasks become feature_pool entries)

Implementation plan

Phase 1: Condition classifier training pipeline (nemish)

  1. Create src/training/condition.pyPoolConditionDataset, ConditionMLP, train_condition_classifier()
  2. Add nemish train-condition CLI subcommand — takes pool-level CSV labels + existing pool detection model
  3. Add extract_color_features() to image_utils or new classify module
  4. Create synthetic training data from existing labeled pools in nidus-sync (export pool condition → tile coordinates → extract features → train)

Phase 2: Daemon integration

  1. Add classify_pool_condition() to the daemon _inference_worker
  2. Add condition to _submit_result() payload
  3. Save condition classifier model alongside the pool detection archive (bundled in /data/model/)

Phase 3: nidus-sync changes

  1. Add conditions form field to postVisionTaskInference handler
  2. Store condition in the geometry properties during task → pool materialization
  3. Update vision_analysis_task schema if needed for condition tracking

Out of scope (Phase 1)

  • Full multi-class segmentation training (pixel-level condition labels)
  • The nidus-sync UI for condition review (that's the Reveal UI, issue #174 area)
  • Integration with the Nidus Core ontology

Risks and mitigations

Risk Mitigation
Pool condition is subtle — two pools that look similar may have different conditions The two-stage approach lets us use richer features (spectral bands, texture) without retraining the detection model. HSV hue is surprisingly robust for water color classification.
"Covered" pools look very different from open pools and may be missed by the binary pool detector Train covered-pool detection separately or augment training data with covered examples. The heuristic pre-classifier catches obvious covers.
Condition labels are scarce The MLP trains on pool-level labels which are cheaper to produce than pixel masks. Existing nidus-sync pool data may already have conditions from field surveys.
Inference latency per pool The MLP is < 1ms per pool on CPU. Even with 100+ pools per tile, this adds < 100ms. No GPU needed for condition classification.
# Pool Condition Detection in Nemish — Design Proposal ## Context ### Current nemish pipeline ``` Satellite tile → [binary segmentation model] → pool mask (bg=0, pool=1) → connected components (CV2) → polygon fitting (circular/rectangular/irregular) → WGS84 GeoJSON geometry → submit mask + geometry to nidus-sync ``` ### nidus-sync pool condition model `feature_pool_state` tracks condition as an enum with values already defined: | Value | Visual meaning | |-------|---------------| | `blue` | Maintained (clear blue water) | | `dry` | Empty / no water | | `green` | Unmaintained (algae) | | `murky` | Deep or turbid water, can't see bottom | | `covered` | Pool cover or solar blanket | | `false pool` | Not actually a pool (operational, not visual) | | `unknown` | Default when condition isn't determined | The detectable conditions **by satellite imagery color analysis** are: **blue, dry, green, murky, covered**. I'll design for these five + background. --- ## Part 1: Training System Changes ### Analysis of approaches #### Option A: Multi-class semantic segmentation (pixel-level) Change the model from binary (pool vs background) to N-class where each class = condition: - Class 0 = background - Class 1 = blue (maintained) - Class 2 = green (unmaintained) - Class 3 = dry - Class 4 = murky - Class 5 = covered **What changes in the code:** 1. **Config** (`default.yaml`): `data.num_classes` → 6 (or N+1) 2. **Training masks**: need per-pixel condition labels (0-5 integer). The existing `PoolDataset` validates for binary 0/255 and exits on unexpected values — a new dataset variant or a mode flag is needed. 3. **Loss function**: `CombinedLoss(CrossEntropy + Dice)` already works for multi-class. Likely adjust `ce_weight`/`dice_weight` since Dice can be noisy with many rare classes. 4. **Metrics** (`compute_iou`, `compute_dice`): already handle `num_classes` > 2. Each class gets its own IoU/Dice report. 5. **Inference** (`predict_array`): still works — returns per-pixel argmax over N classes. 6. **Post-processing**: for each connected component in the pool mask, aggregate pixel-level condition predictions to get a single pool-level condition (majority vote, or max-confidence vote). **Challenges:** - Requires existing training masks to be re-labeled at pixel granularity with condition. That's a big annotation lift. - A pool can legitimately have mixed color pixels (shadows, reflections, edge effects). Pixel-level noise needs smoothing. - The SMP UNet decoder channels grow from 2→6, which marginally increases parameter count but doesn't change architecture. - Rare conditions (e.g., covered) may be heavily class-imbalanced, requiring class weighting. #### Option B: Two-stage (pool detection → condition classifier) ★ RECOMMENDED ★ Stage 1 (unchanged): binary pool segmentation as today. Stage 2: for each detected pool polygon, classify its condition. Two viable sub-approaches for Stage 2: **B1 — Lightweight color-statistics classifier:** - Extract per-pixel (RGB) values from the satellite tile within each pool mask - Compute features: mean(R,G,B), median, std dev, quantiles, HSV hue histogram - Classify with a small model (MLP with 2 hidden layers, < 50K params) or even sklearn RandomForest - Training data: patches of known pools with condition labels - Strengths: Very fast inference, no GPU needed for stage 2, easy to iterate - Weaknesses: May confuse murky vs blue in some lighting conditions **B2 — CNN patch classifier:** - For each pool polygon, crop the bounding-box region from the satellite tile - Resize to fixed size (e.g., 64×64) and run through a small CNN classifier (ResNet-18, MobileNetV3) - Training data: cropped pool images with condition labels - Strengths: Captures spatial texture (algae patterns, cover wrinkles, shade gradients) - Weaknesses: Needs GPU for efficient inference on many pools; more data-hungry **Why two-stage is better than multi-class segmentation:** 1. **Modularity** — the pool detection pipeline doesn't change. Condition detection is a bolt-on. 2. **Data efficiency** — condition training data can be pool-level labels (much cheaper than pixel-level masks). You can annotate condition at the pool/card level in the UI. 3. **Incremental rollout** — deploy pool detection first (already working), add condition detection later. If condition models are wrong, pool maps are unaffected. 4. **Composability** — you can use different techniques for different conditions (e.g., simple thresholding for "covered" which has very distinct visual signature, ML for subtler brown/green/murky decisions). #### Option C: Multi-task segmentation (shared encoder, two decoders) One shared encoder (ResNet-34) with two decoder heads: - Head 1: Binary pool mask (2 channels) - Head 2: Condition logits (N channels), masked by pool regions This preserves the binary mask quality while learning condition features, but it's the most complex to implement, debug, and train. The codebase doesn't support multi-head architectures yet, and this design doc isn't the place for that scope. ### Recommended approach: Two-stage (Option B2 + B1 hybrid) Start with **B1 (color-statistics MLP)** as the MVP — it's the quickest path to a working condition classifier: - Features: per-pool-pixel RGB mean, median, std, plus HSV hue histogram (32 bins) - Classifier: 2-layer MLP (64→32→N) with ReLU + dropout - Training: pool-level labels only, generated from existing nidus-sync pool data where condition is known As a fallback/Baseline for covered pools (which are very distinct — usually a dark rectangle/blue tarp), add a simple **heuristic pre-classifier**: if mean pixel brightness < threshold and edge density > threshold → "covered". This can be a 20-line function and catch most covered pools with near-perfect accuracy. The MLP classifier training pipeline would be a new subcommand: ``` nemish train-condition \ --pools /data/pool-conditions.csv # pool_id, tile_ref, condition label --checkpoint /data/model/best.pth # existing pool detection model (for mask extraction) ``` --- ## Part 2: Daemon Integration ### What the daemon currently does (in `_inference_worker`) ``` 1. predict_array(image) → (mask, pool_probs) 2. pool_mask = mask > 0 3. For each connected component of pool_mask: a. extract_polygons_from_mask() → polygons b. polygons_to_wgs84_geometry() → geometry GeoJSON 4. Submit mask PNG + confidence + geometry to nidus-sync ``` ### What needs to change for condition detection ``` 1. predict_array(image) → (mask, pool_probs) # unchanged 2. pool_mask = mask > 0 # unchanged 3. For each connected component: a. extract_polygons_from_mask() → polygons # unchanged b. polygons_to_wgs84_geometry() → geometry # unchanged c. classify_pool_condition(image, pool_mask_component) → condition # NEW 4. Submit mask PNG + confidence + geometry + condition to nidus-sync # MODIFIED ``` ### Changes by file #### `src/daemon.py` 1. **Load condition classifier** during inference worker init (alongside the pool detection model): ```python condition_model = load_condition_classifier(model_dir) # small MLP or ONNX ``` 2. **New function — `classify_pool_condition()`**: ```python def classify_pool_condition( tile_image: np.ndarray, # original RGB tile pool_mask: np.ndarray, # binary mask for one pool classifier: Any, # loaded condition classifier image_size: int = 256, ) -> tuple[str, float]: """Return (condition_label, confidence) for a single pool.""" # Crop the pool bounding box from the tile ys, xs = np.where(pool_mask > 0) xmin, ymin, xmax, ymax = xs.min(), ys.min(), xs.max(), ys.max() pool_crop = tile_image[ymin:ymax, xmin:xmax] pool_mask_crop = pool_mask[ymin:ymax, xmin:xmax] # Extract per-pixel color features (masked within pool boundaries) features = extract_color_features(pool_crop, pool_mask_crop) # Run classifier return classifier.predict(features) # -> ("blue", 0.92) ``` 3. **Modified `_submit_result()`** — also include condition in the payload: ```python data = { "confidence": str(result["confidence"]), "conditions": json.dumps(result["conditions"]), # List[pool_condition] } ``` Where `conditions` is a list of per-pool conditions matching the order of polygons in the geometry. #### `src/inference/predict.py` Add a new condition classifier module (or a new file `src/classify.py`): ```python def load_condition_classifier(model_path: str, device: str = "cpu"): """Load the condition classification model.""" ... def extract_color_features(crop: np.ndarray, mask: np.ndarray) -> np.ndarray: """Extract per-pixel color statistics from the masked pool region. Returns a feature vector: [mean_r, mean_g, mean_b, std_r, std_g, std_b, median_r, median_g, median_b, hue_hist_0..31] (41 features) """ ... ``` #### `src/training/condition_classifier.py` (new file) Training pipeline for the condition MLP classifier: ```python class PoolConditionDataset(Dataset): """Pool-level condition labels with tile image references.""" class ConditionMLP(nn.Module): """Small multi-layer perceptron for condition classification.""" def train_condition_classifier(cfg): """Training loop for the condition classifier.""" ``` #### nidus-sync side The **`/api/vision/task/{id}/inference`** endpoint needs to accept a `conditions` field. Currently it reads `confidence` (form field) and `mask` (file). Adding: ```go conditionsStr := r.FormValue("conditions") // JSON array of condition strings ``` The `vision_analysis_task` table already has `confidence`. We'd add a new migration to store per-task conditions. However, since a task (tile) may contain multiple pools with different conditions, the condition belongs on the **geometry features** or in a separate `task_pool_conditions` table. **Recommended approach**: Store conditions as a property on the geometry GeoJSON. Each polygon Feature gets a `condition` property. The existing GeoJSON pipeline already attaches per-polygon properties (`id`, `area_pixels`, `num_vertices`). Just add `condition`. The `postVisionTaskInference` handler already receives `geometry` in form data (optional from the daemon, though currently unused in that handler — geometry is separately PUT via `/vision/task/{id}`). We should: 1. Add a `conditions` form field to the inference POST 2. Store conditions alongside geometry properties when the analysis is later materialized into pool records (in the `vision_analysis.go` flow where tasks become `feature_pool` entries) --- ## Implementation plan ### Phase 1: Condition classifier training pipeline (nemish) 1. **Create `src/training/condition.py`** — `PoolConditionDataset`, `ConditionMLP`, `train_condition_classifier()` 2. **Add `nemish train-condition` CLI subcommand** — takes pool-level CSV labels + existing pool detection model 3. **Add `extract_color_features()`** to image_utils or new classify module 4. **Create synthetic training data** from existing labeled pools in nidus-sync (export pool condition → tile coordinates → extract features → train) ### Phase 2: Daemon integration 5. **Add `classify_pool_condition()` to the daemon `_inference_worker`** 6. **Add condition to `_submit_result()` payload** 7. **Save condition classifier model alongside the pool detection archive** (bundled in `/data/model/`) ### Phase 3: nidus-sync changes 8. **Add `conditions` form field to `postVisionTaskInference`** handler 9. **Store condition in the geometry properties** during task → pool materialization 10. **Update `vision_analysis_task` schema** if needed for condition tracking ### Out of scope (Phase 1) - Full multi-class segmentation training (pixel-level condition labels) - The nidus-sync UI for condition review (that's the Reveal UI, issue #174 area) - Integration with the Nidus Core ontology --- ## Risks and mitigations | Risk | Mitigation | |------|-----------| | Pool condition is subtle — two pools that look similar may have different conditions | The two-stage approach lets us use richer features (spectral bands, texture) without retraining the detection model. HSV hue is surprisingly robust for water color classification. | | "Covered" pools look very different from open pools and may be missed by the binary pool detector | Train covered-pool detection separately or augment training data with covered examples. The heuristic pre-classifier catches obvious covers. | | Condition labels are scarce | The MLP trains on pool-level labels which are cheaper to produce than pixel masks. Existing nidus-sync pool data may already have conditions from field surveys. | | Inference latency per pool | The MLP is < 1ms per pool on CPU. Even with 100+ pools per tile, this adds < 100ms. No GPU needed for condition classification. |
Author
Owner

Great, this is a good plan. I agree that option B is preferrable, and I like your recommendation of Option B2 + B1 hybrid. I also think that including the condition in the GeoJSON payload properties is an elegant solution.

Please create a pull request detailing the design and implementation plan with steps for validation that things are working at each phase. Put it in doc/pool-condition-detection.md

Great, this is a good plan. I agree that option B is preferrable, and I like your recommendation of Option B2 + B1 hybrid. I also think that including the condition in the GeoJSON payload properties is an elegant solution. Please create a pull request detailing the design and implementation plan with steps for validation that things are working at each phase. Put it in doc/pool-condition-detection.md
Member

PR #10 created with the design document: doc/pool-condition-detection.md.

#10

Covers:

  • Architecture: two-stage hybrid (B2 multi-class segmentation + B1 per-pool MLP)
  • 5 implementation phases with per-phase validation checkpoints
  • Training pipeline changes (mask conversion, ConditionSegmentationSpec, condition config)
  • Daemon changes (condition classifier integration, per-polygon GeoJSON properties)
  • Label Studio annotation updates
  • Risk assessment + mitigations

Ready for review, neighborino!

PR #10 created with the design document: `doc/pool-condition-detection.md`. https://source.gleipnir.technology/Gleipnir/nemish/pulls/10 Covers: - Architecture: two-stage hybrid (B2 multi-class segmentation + B1 per-pool MLP) - 5 implementation phases with per-phase validation checkpoints - Training pipeline changes (mask conversion, ConditionSegmentationSpec, condition config) - Daemon changes (condition classifier integration, per-polygon GeoJSON properties) - Label Studio annotation updates - Risk assessment + mitigations Ready for review, neighborino!
Member

CI failure on main after merge

PR #10 was merged, but the merge CI run (run 188) will fail — there is a pre-existing lint issue in src/web/server.py: an unused # noqa: N802 directive on do_GET(). Ruff 0.15.x doesn't flag N802 on BaseHTTPRequestHandler method names, so the noqa is flagged as unused (RUF100). This existed before the design doc commit, but wasn't caught because the CI config was iterated on main separately.

Created PR #11 as a one-line fix (remove the stale noqa comment): #11

Once #11 is merged, main CI should pass cleanly again.

**CI failure on main after merge** PR #10 was merged, but the merge CI run (run 188) will fail — there is a pre-existing lint issue in `src/web/server.py`: an unused `# noqa: N802` directive on `do_GET()`. Ruff 0.15.x doesn't flag N802 on `BaseHTTPRequestHandler` method names, so the noqa is flagged as unused (RUF100). This existed before the design doc commit, but wasn't caught because the CI config was iterated on main separately. **Created PR #11** as a one-line fix (remove the stale noqa comment): https://source.gleipnir.technology/Gleipnir/nemish/pulls/11 Once #11 is merged, main CI should pass cleanly again.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Gleipnir/nemish#9
No description provided.