File size: 6,495 Bytes
eeeea78
d3839bb
eeeea78
 
 
d3839bb
eeeea78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d3839bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeeea78
 
 
 
 
 
d3839bb
 
 
eeeea78
 
d3839bb
eeeea78
 
d3839bb
eeeea78
d3839bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeeea78
 
 
 
 
 
 
 
d3839bb
 
 
eeeea78
d3839bb
 
eeeea78
 
 
d3839bb
 
 
 
eeeea78
 
 
d3839bb
 
 
eeeea78
d3839bb
 
 
 
eeeea78
 
d3839bb
 
 
eeeea78
 
 
 
 
 
 
d3839bb
eeeea78
d3839bb
 
 
 
eeeea78
 
 
d3839bb
eeeea78
d3839bb
 
 
 
eeeea78
 
 
 
 
 
 
 
 
 
 
 
 
 
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import os
import sys
import logging
from typing import Optional
from functools import lru_cache
from pathlib import Path

# -------------------------------------------------
# Logging setup
# -------------------------------------------------
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# -------------------------------------------------
# TensorFlow import (lazy + fault-tolerant)
# -------------------------------------------------
try:
    import tensorflow as tf
    TF_AVAILABLE = True
    logger.info("TensorFlow successfully imported.")
except ImportError as e:
    TF_AVAILABLE = False
    tf = None
    logger.warning("TensorFlow import failed: %s", e)

# -------------------------------------------------
# Helper: Resolve absolute model directory
# -------------------------------------------------
def _get_models_dir() -> str:
    """Return absolute path to the models directory."""
    base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    models_dir = os.path.join(base_dir, "models")
    
    # Log diagnostic information
    logger.info("=" * 70)
    logger.info("MODEL DIRECTORY RESOLUTION")
    logger.info(f"Current working directory: {os.getcwd()}")
    logger.info(f"Script location: {__file__}")
    logger.info(f"Base directory: {base_dir}")
    logger.info(f"Models directory: {models_dir}")
    logger.info(f"Models directory exists: {os.path.exists(models_dir)}")
    
    if os.path.exists(models_dir):
        logger.info(f"Models directory contents: {os.listdir(models_dir)}")
    else:
        logger.error(f"❌ Models directory NOT FOUND: {models_dir}")
        logger.error(f"Current directory contents: {os.listdir('.')}")
        # Try alternative paths
        alt_paths = ["models", "./models", "/app/models"]
        for alt_path in alt_paths:
            if os.path.exists(alt_path):
                logger.info(f"βœ… Found models at alternative path: {alt_path}")
                logger.info(f"Contents: {os.listdir(alt_path)}")
                models_dir = os.path.abspath(alt_path)
                break
    
    logger.info("=" * 70)
    return models_dir

# -------------------------------------------------
# Helper: Construct model path safely
# -------------------------------------------------
def _resolve_model_path(model_env_key: str, default_filename: str) -> str:
    """Resolve model path from env var or fallback to /models directory."""
    logger.info(f"Resolving model path for: {default_filename}")
    
    # Check environment variable first
    env_path = os.getenv(model_env_key)
    if env_path and os.path.exists(env_path):
        logger.info(f"βœ… Using model from env var {model_env_key}: {env_path}")
        return env_path

    # Fallback to models directory
    fallback_path = os.path.join(_get_models_dir(), default_filename)
    
    if os.path.exists(fallback_path):
        file_size = os.path.getsize(fallback_path)
        logger.info(f"βœ… Model file found: {fallback_path}")
        logger.info(f"πŸ“Š File size: {file_size:,} bytes ({file_size/1024:.2f} KB)")
    else:
        logger.error(f"❌ Model file missing: {fallback_path}")
        
        # Try to find the file in current directory as last resort
        if os.path.exists(default_filename):
            logger.info(f"βœ… Found model in current directory: {default_filename}")
            return os.path.abspath(default_filename)
        
        models_in_cwd = os.path.join("models", default_filename)
        if os.path.exists(models_in_cwd):
            logger.info(f"βœ… Found model in ./models/: {models_in_cwd}")
            return os.path.abspath(models_in_cwd)
    
    return fallback_path

# -------------------------------------------------
# Generic model loader with caching
# -------------------------------------------------
@lru_cache(maxsize=4)
def _load_model(model_path: str) -> Optional[object]:
    """Load a Keras model once and cache it."""
    logger.info("=" * 70)
    logger.info(f"πŸ“₯ LOADING MODEL: {model_path}")
    
    if not TF_AVAILABLE:
        logger.error("❌ TensorFlow not available. Cannot load model: %s", model_path)
        logger.info("=" * 70)
        return None

    if not os.path.exists(model_path):
        logger.error(f"❌ Model file not found: {model_path}")
        logger.error(f"Current working directory: {os.getcwd()}")
        logger.error(f"Absolute path: {os.path.abspath(model_path)}")
        logger.info("=" * 70)
        return None

    try:
        file_size = os.path.getsize(model_path)
        logger.info(f"πŸ“Š Model file size: {file_size:,} bytes ({file_size/1024/1024:.2f} MB)")
        
        model = tf.keras.models.load_model(model_path)
        logger.info(f"βœ… Model loaded successfully: {model_path}")
        logger.info(f"Model input shape: {model.input_shape}")
        logger.info(f"Model output shape: {model.output_shape}")
        logger.info("=" * 70)
        return model
    except Exception as e:
        logger.error(f"❌ Failed to load model: {model_path}")
        logger.exception("Exception details: %s", e)
        logger.info("=" * 70)
        return None

# -------------------------------------------------
# Public APIs (for routes)
# -------------------------------------------------
def load_xception_model() -> Optional[object]:
    """Load and return XceptionNet model."""
    logger.info("πŸ”§ Loading XceptionNet model...")
    path = _resolve_model_path("MODEL_PATH_XCEPTION", "xceptionnet.keras")
    model = _load_model(path)
    if model is None:
        logger.error("❌ XceptionNet model failed to load!")
    return model

def load_mesonet_model() -> Optional[object]:
    """Load and return MesoNet model."""
    logger.info("πŸ”§ Loading MesoNet model...")
    path = _resolve_model_path("MODEL_PATH_MESONET", "mesonet.keras")
    model = _load_model(path)
    if model is None:
        logger.error("❌ MesoNet model failed to load!")
    return model

def clear_model_cache():
    """Clear in-memory cache (useful for memory control or testing)."""
    _load_model.cache_clear()
    logger.info("Model cache cleared.")

# -------------------------------------------------
# Health check utility
# -------------------------------------------------
def verify_models_ready() -> bool:
    """Verify both models are loaded successfully."""
    xception_ok = load_xception_model() is not None
    mesonet_ok = load_mesonet_model() is not None
    return xception_ok and mesonet_ok