File size: 5,999 Bytes
61b3c2b
60e6efb
61b3c2b
 
 
dd1d7f5
61b3c2b
 
dd1d7f5
61b3c2b
 
60e6efb
f8ac29e
 
61b3c2b
 
60e6efb
 
 
 
 
 
 
 
 
 
61b3c2b
 
 
 
 
7613956
 
61b3c2b
e768711
 
 
 
dd1d7f5
 
 
 
 
 
61b3c2b
f8ac29e
61b3c2b
60e6efb
f8ac29e
60e6efb
 
 
 
 
 
61b3c2b
60e6efb
 
 
61b3c2b
60e6efb
31ddfa7
 
 
 
 
 
 
 
 
61b3c2b
c170961
dd1d7f5
 
 
c170961
dd1d7f5
 
c170961
dd1d7f5
 
 
 
61b3c2b
e768711
 
 
5f6c42c
93d0941
e768711
 
 
 
5f6c42c
 
93d0941
 
e768711
 
 
 
 
 
 
c170961
e768711
 
 
 
6926cc6
93d0941
 
60e6efb
6926cc6
60e6efb
 
 
f8ac29e
 
 
 
 
 
 
 
 
 
 
 
 
 
60e6efb
 
 
 
6926cc6
60e6efb
e768711
 
 
 
 
61b3c2b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from pathlib import Path
from typing import Dict, Callable, Optional, Generator, Any
import shutil
from PIL import Image
import glob
import os

from sorghum_pipeline.pipeline import SorghumPipeline
from sorghum_pipeline.config import Config, Paths


def run_pipeline_on_image(input_image_path: str, work_dir: str, save_artifacts: bool = True, 
                          progress_callback: Optional[Callable[[str, Dict[str, Any]], None]] = None,
                          single_plant_mode: bool = False) -> Generator[Dict[str, str], None, None]:
    """
    Run sorghum pipeline on a single image (no instance segmentation).
    Yields dict[label -> image_path] progressively for gallery display.
    
    Args:
        input_image_path: Path to input image
        work_dir: Working directory for outputs
        save_artifacts: Whether to save artifacts
        progress_callback: Optional callback(stage_name, data) called after each pipeline stage
    
    Yields:
        Dictionary of output paths progressively as they become available
    """

    work = Path(work_dir)
    work.mkdir(parents=True, exist_ok=True)

    # Use input path directly (already in work_dir from app.py)
    input_path = Path(input_image_path)

    # Ensure demo env vars are set before pipeline construction
    os.environ['MINIMAL_DEMO'] = '1'
    os.environ['FAST_OUTPUT'] = '1'

    # Build in-memory config pointing input/output to the working directory
    cfg = Config()
    cfg.paths = Paths(
        input_folder=str(work),
        output_folder=str(work),
        boundingbox_dir=str(work)
    )
    pipeline = SorghumPipeline(config=cfg, single_plant_mode=single_plant_mode)

    # Run the pipeline with progress callback (generator)
    for stage_result in pipeline.run_with_progress(single_image_path=str(input_path), progress_callback=progress_callback, single_plant_mode=single_plant_mode):
        # Yield intermediate outputs as they become available
        outputs = _collect_outputs(work, stage_result.get('plants', {}))
        yield outputs
    
    # Final results
    results = stage_result


def _collect_outputs(work: Path, plants: Dict[str, Any]) -> Dict[str, str]:
    """Collect all available outputs from work directory and plants data."""
    outputs: Dict[str, str] = {}
    
    try:
        # Log immediate output directory contents for debugging
        for sub in ['results', 'Vegetation_indices_images', 'texture_output']:
            p = work / sub
            if p.exists():
                files = sorted([str(x.name) for x in p.iterdir() if x.is_file()])
                print(f"Artifacts in {sub}: {files}")
    except Exception:
        pass

    # Collect desired vegetation indices (replace ARI with SAVI)
    wanted = [
        work / 'Vegetation_indices_images/ndvi.png',
        work / 'Vegetation_indices_images/gndvi.png',
        work / 'Vegetation_indices_images/savi.png',
    ]
    labels = [
        'NDVI', 'GNDVI', 'SAVI',
    ]
    for label, path in zip(labels, wanted):
        if path.exists():
            outputs[label] = str(path)

    # Also include overlay and mask if present
    overlay_path = work / 'results/overlay.png'
    mask_path = work / 'results/mask.png'
    composite_path = work / 'results/composite.png'
    input_img_path = work / 'results/input_image.png'
    if overlay_path.exists():
        outputs['Overlay'] = str(overlay_path)
    if mask_path.exists():
        outputs['Mask'] = str(mask_path)
    if composite_path.exists():
        outputs['Composite'] = str(composite_path)
    if input_img_path.exists():
        outputs['InputImage'] = str(input_img_path)

    # Extract simple stats for display if present in pipeline results
    try:
        if plants:
            _, pdata = next(iter(plants.items()))
            veg = pdata.get('vegetation_indices', {})
            stats_lines = []
            for name in ['NDVI', 'GNDVI', 'SAVI']:
                entry = veg.get(name, {})
                st = entry.get('statistics', {}) if isinstance(entry, dict) else {}
                if st:
                    stats_lines.append(f"{name}: mean={st.get('mean', 0):.3f}, std={st.get('std', 0):.3f}")
            # Morphology stats (height - always show as single plant)
            morph = pdata.get('morphology_features', {}) if isinstance(pdata, dict) else {}
            traits = morph.get('traits', {}) if isinstance(morph, dict) else {}
            
            # Get plant height (system now filters to largest plant only)
            plant_heights = traits.get('plant_heights', {})
            num_plants = traits.get('num_plants', 0)
            
            # Display plant info based on mode
            if num_plants > 0 and isinstance(plant_heights, dict):
                if num_plants == 1 or len(plant_heights) == 1:
                    # Single plant display
                    height_cm = list(plant_heights.values())[0]
                    stats_lines.append(f"Number of plants: 1")
                    stats_lines.append(f"Plant height: {height_cm:.2f} cm")
                else:
                    # Multiple plants display
                    stats_lines.append(f"Number of plants: {num_plants}")
                    sorted_plants = sorted(plant_heights.items(), key=lambda x: int(x[0].split('_')[1]))
                    for plant_name, height_cm in sorted_plants:
                        plant_num = plant_name.split('_')[1]
                        stats_lines.append(f"  Plant {plant_num}: {height_cm:.2f} cm")
            else:
                # Fallback to old single height field
                height_cm = traits.get('plant_height_cm')
                if isinstance(height_cm, (int, float)) and height_cm > 0:
                    stats_lines.append(f"Number of plants: 1")
                    stats_lines.append(f"Plant height: {height_cm:.2f} cm")
            if stats_lines:
                outputs['StatsText'] = "\n".join(stats_lines)
    except Exception:
        pass

    return outputs