|
|
""" |
|
|
Minimal output manager for demo (saves only 7 required images). |
|
|
""" |
|
|
|
|
|
import os |
|
|
import numpy as np |
|
|
import cv2 |
|
|
import matplotlib |
|
|
if os.environ.get('MPLBACKEND') is None: |
|
|
matplotlib.use('Agg') |
|
|
import matplotlib.pyplot as plt |
|
|
import matplotlib.cm as cm |
|
|
from pathlib import Path |
|
|
from typing import Dict, Any |
|
|
import logging |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class OutputManager: |
|
|
"""Minimal output manager for demo.""" |
|
|
|
|
|
def __init__(self, output_folder: str, settings: Any): |
|
|
"""Initialize output manager.""" |
|
|
self.output_folder = Path(output_folder) |
|
|
self.settings = settings |
|
|
try: |
|
|
self.minimal_demo: bool = bool(int(os.environ.get('MINIMAL_DEMO', '0'))) |
|
|
except Exception: |
|
|
self.minimal_demo = False |
|
|
self.output_folder.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
def create_output_directories(self) -> None: |
|
|
"""Create output directories.""" |
|
|
self.output_folder.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
def save_plant_results(self, plant_key: str, plant_data: Dict[str, Any]) -> None: |
|
|
"""Save minimal demo outputs only.""" |
|
|
if not self.minimal_demo: |
|
|
logger.warning("OutputManager configured for minimal demo only") |
|
|
return |
|
|
|
|
|
self._save_minimal_demo_outputs(plant_data) |
|
|
|
|
|
def _save_minimal_demo_outputs(self, plant_data: Dict[str, Any]) -> None: |
|
|
"""Save only the 7 required images.""" |
|
|
results_dir = self.output_folder / 'results' |
|
|
veg_dir = self.output_folder / 'Vegetation_indices_images' |
|
|
tex_dir = self.output_folder / 'texture_output' |
|
|
results_dir.mkdir(parents=True, exist_ok=True) |
|
|
veg_dir.mkdir(parents=True, exist_ok=True) |
|
|
tex_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
|
|
|
try: |
|
|
mask = plant_data.get('mask') |
|
|
if isinstance(mask, np.ndarray): |
|
|
cv2.imwrite(str(results_dir / 'mask.png'), mask) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save mask: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
base_image = plant_data.get('composite') |
|
|
mask = plant_data.get('mask') |
|
|
if isinstance(base_image, np.ndarray) and isinstance(mask, np.ndarray): |
|
|
overlay = self._create_overlay(base_image, mask) |
|
|
cv2.imwrite(str(results_dir / 'overlay.png'), overlay) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save overlay: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
veg = plant_data.get('vegetation_indices', {}) |
|
|
for name in ['NDVI', 'ARI', 'GNDVI']: |
|
|
data = veg.get(name, {}) |
|
|
values = data.get('values') if isinstance(data, dict) else None |
|
|
if isinstance(values, np.ndarray) and values.size > 0: |
|
|
try: |
|
|
cmap = cm.RdYlGn if name in ['NDVI', 'GNDVI'] else cm.magma |
|
|
vmin, vmax = (-1, 1) if name in ['NDVI', 'GNDVI'] else (0, 1) |
|
|
|
|
|
masked = np.ma.masked_invalid(values.astype(np.float64)) |
|
|
fig, ax = plt.subplots(figsize=(5, 5)) |
|
|
ax.set_axis_off() |
|
|
ax.set_facecolor('white') |
|
|
ax.imshow(masked, cmap=cmap, vmin=vmin, vmax=vmax) |
|
|
plt.tight_layout() |
|
|
plt.savefig(veg_dir / f"{name.lower()}.png", dpi=100, bbox_inches='tight') |
|
|
plt.close(fig) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save {name}: {e}") |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save vegetation indices: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
tex = plant_data.get('texture_features', {}) |
|
|
color_band = tex.get('color', {}) |
|
|
feats = color_band.get('features', {}) |
|
|
|
|
|
if isinstance(feats.get('lbp'), np.ndarray) and feats['lbp'].size > 0: |
|
|
cv2.imwrite(str(tex_dir / 'lbp.png'), feats['lbp'].astype(np.uint8)) |
|
|
|
|
|
if isinstance(feats.get('hog'), np.ndarray) and feats['hog'].size > 0: |
|
|
cv2.imwrite(str(tex_dir / 'hog.png'), feats['hog'].astype(np.uint8)) |
|
|
|
|
|
lac = feats.get('lac2') |
|
|
if isinstance(lac, np.ndarray) and lac.size > 0: |
|
|
if lac.dtype != np.uint8: |
|
|
lac = self._normalize_to_uint8(lac.astype(np.float64)) |
|
|
cv2.imwrite(str(tex_dir / 'lacunarity.png'), lac) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save texture: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
morph = plant_data.get('morphology_features', {}) |
|
|
images = morph.get('images', {}) |
|
|
size_img = images.get('size_analysis') |
|
|
if isinstance(size_img, np.ndarray) and size_img.size > 0: |
|
|
cv2.imwrite(str(results_dir / 'size.size_analysis.png'), size_img) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to save size analysis: {e}") |
|
|
|
|
|
def _create_overlay(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray: |
|
|
"""Create overlay (masked pixels only).""" |
|
|
if mask is None: |
|
|
return image |
|
|
if mask.shape[:2] != image.shape[:2]: |
|
|
mask = cv2.resize(mask.astype(np.uint8), (image.shape[1], image.shape[0]), |
|
|
interpolation=cv2.INTER_NEAREST) |
|
|
binary = (mask.astype(np.int32) > 0).astype(np.uint8) * 255 |
|
|
return cv2.bitwise_and(image, image, mask=binary) |
|
|
|
|
|
def _normalize_to_uint8(self, arr: np.ndarray) -> np.ndarray: |
|
|
"""Normalize to uint8.""" |
|
|
arr = np.nan_to_num(arr, nan=0.0, posinf=0.0, neginf=0.0) |
|
|
if arr.ptp() > 0: |
|
|
normalized = (arr - arr.min()) / (arr.ptp() + 1e-6) * 255 |
|
|
else: |
|
|
normalized = np.zeros_like(arr) |
|
|
return np.clip(normalized, 0, 255).astype(np.uint8) |