coderuday21 Cursor commited on
Commit
16caeeb
·
1 Parent(s): c506ed2

Fix AdaptFormer on production: pin protobuf and expose model health in /health

Browse files
Files changed (4) hide show
  1. Dockerfile +3 -2
  2. app/main.py +11 -3
  3. app/model_inference.py +27 -1
  4. requirements.txt +2 -1
Dockerfile CHANGED
@@ -19,14 +19,15 @@ WORKDIR /app
19
 
20
  # Build-time info + cache-bust:
21
  # Changing APP_BUILD forces Docker to re-run subsequent layers (including pip install).
22
- ARG APP_BUILD=23
23
  ENV APP_BUILD=${APP_BUILD}
24
  RUN echo "Docker build start: APP_BUILD=${APP_BUILD}" && python -V
25
 
26
  # Install Python dependencies
27
  COPY requirements.txt .
28
  RUN pip install --no-cache-dir --disable-pip-version-check --default-timeout=300 -U pip setuptools wheel
29
- RUN pip install --no-cache-dir --disable-pip-version-check --default-timeout=300 --prefer-binary -r requirements.txt -v
 
30
 
31
  # Pre-download the AdaptFormer model so cold starts are instant
32
  ENV HF_HOME=/app/.hf_cache
 
19
 
20
  # Build-time info + cache-bust:
21
  # Changing APP_BUILD forces Docker to re-run subsequent layers (including pip install).
22
+ ARG APP_BUILD=24
23
  ENV APP_BUILD=${APP_BUILD}
24
  RUN echo "Docker build start: APP_BUILD=${APP_BUILD}" && python -V
25
 
26
  # Install Python dependencies
27
  COPY requirements.txt .
28
  RUN pip install --no-cache-dir --disable-pip-version-check --default-timeout=300 -U pip setuptools wheel
29
+ RUN pip install --no-cache-dir --disable-pip-version-check --default-timeout=300 --prefer-binary -r requirements.txt -v \
30
+ && python -c "import google.protobuf; from transformers import AutoImageProcessor; print('protobuf', google.protobuf.__version__, 'transformers ok')"
31
 
32
  # Pre-download the AdaptFormer model so cold starts are instant
33
  ENV HF_HOME=/app/.hf_cache
app/main.py CHANGED
@@ -70,14 +70,22 @@ except Exception as e:
70
  import logging
71
  logging.getLogger("uvicorn.error").warning("Startup migration skipped: %s", e)
72
 
73
- app = FastAPI(title="AI Change Detection", version="2.2.0")
74
 
75
 
76
  @app.get("/health")
77
  def health():
78
- """Lightweight health check so Hugging Face can mark the Space as running quickly."""
79
  from datetime import datetime
80
- return {"status": "ok", "version": "2.2.0", "server_time_ist": _isoformat_ist(datetime.now(timezone.utc))}
 
 
 
 
 
 
 
 
81
 
82
 
83
  @app.on_event("startup")
 
70
  import logging
71
  logging.getLogger("uvicorn.error").warning("Startup migration skipped: %s", e)
72
 
73
+ app = FastAPI(title="AI Change Detection", version="2.2.1")
74
 
75
 
76
  @app.get("/health")
77
  def health():
78
+ """Health check + AdaptFormer model status (HF Spaces + diagnostics)."""
79
  from datetime import datetime
80
+ from .model_inference import get_model_status
81
+
82
+ model = get_model_status()
83
+ return {
84
+ "status": "ok" if model.get("available") else "degraded",
85
+ "version": "2.2.1",
86
+ "server_time_ist": _isoformat_ist(datetime.now(timezone.utc)),
87
+ "adaptFormer": model,
88
+ }
89
 
90
 
91
  @app.on_event("startup")
app/model_inference.py CHANGED
@@ -22,6 +22,7 @@ _MODEL_ID = "deepang/adaptformer-LEVIR-CD"
22
  _TILE_SIZE = 256 # LEVIR-CD native patch size
23
  _AVAILABLE = None
24
  _LOAD_FAILED = False
 
25
 
26
 
27
  def _try_import():
@@ -34,7 +35,7 @@ def _try_import():
34
 
35
 
36
  def _load_model():
37
- global _MODEL, _PROCESSOR, _DEVICE, _AVAILABLE, _LOAD_FAILED
38
  if _MODEL is not None:
39
  return _MODEL, _PROCESSOR
40
  if _LOAD_FAILED:
@@ -56,10 +57,12 @@ def _load_model():
56
  _MODEL.to(_DEVICE)
57
  _MODEL.eval()
58
  _AVAILABLE = True
 
59
  logger.info("AdaptFormer loaded on %s", _DEVICE)
60
  except Exception as exc:
61
  _LOAD_FAILED = True
62
  _AVAILABLE = False
 
63
  logger.error("AdaptFormer load failed: %s", exc)
64
  raise
65
  return _MODEL, _PROCESSOR
@@ -81,15 +84,38 @@ def is_model_available():
81
 
82
  def preload_model():
83
  """Warm-load AdaptFormer at app startup (best-effort)."""
 
84
  try:
85
  _load_model()
86
  logger.info("AdaptFormer preload complete")
87
  return True
88
  except Exception as exc:
 
89
  logger.warning("AdaptFormer preload skipped: %s", exc)
90
  return False
91
 
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  def predict_change_mask(img1, img2, threshold=0.5):
94
  """
95
  Run AdaptFormer inference on two RGB numpy arrays (H, W, 3).
 
22
  _TILE_SIZE = 256 # LEVIR-CD native patch size
23
  _AVAILABLE = None
24
  _LOAD_FAILED = False
25
+ _LOAD_ERROR: str | None = None
26
 
27
 
28
  def _try_import():
 
35
 
36
 
37
  def _load_model():
38
+ global _MODEL, _PROCESSOR, _DEVICE, _AVAILABLE, _LOAD_FAILED, _LOAD_ERROR
39
  if _MODEL is not None:
40
  return _MODEL, _PROCESSOR
41
  if _LOAD_FAILED:
 
57
  _MODEL.to(_DEVICE)
58
  _MODEL.eval()
59
  _AVAILABLE = True
60
+ _LOAD_ERROR = None
61
  logger.info("AdaptFormer loaded on %s", _DEVICE)
62
  except Exception as exc:
63
  _LOAD_FAILED = True
64
  _AVAILABLE = False
65
+ _LOAD_ERROR = str(exc)
66
  logger.error("AdaptFormer load failed: %s", exc)
67
  raise
68
  return _MODEL, _PROCESSOR
 
84
 
85
  def preload_model():
86
  """Warm-load AdaptFormer at app startup (best-effort)."""
87
+ global _LOAD_ERROR
88
  try:
89
  _load_model()
90
  logger.info("AdaptFormer preload complete")
91
  return True
92
  except Exception as exc:
93
+ _LOAD_ERROR = str(exc)
94
  logger.warning("AdaptFormer preload skipped: %s", exc)
95
  return False
96
 
97
 
98
+ def get_model_status() -> dict:
99
+ """Status for /health — shows whether AI detection or classical fallback is active."""
100
+ if _AVAILABLE is True:
101
+ mode = "adaptformer_gated_fusion"
102
+ available = True
103
+ elif _LOAD_FAILED:
104
+ mode = "classical_fallback"
105
+ available = False
106
+ else:
107
+ available = is_model_available()
108
+ mode = "adaptformer_gated_fusion" if available else "classical_fallback"
109
+
110
+ return {
111
+ "modelId": _MODEL_ID,
112
+ "available": available,
113
+ "detectionMode": mode,
114
+ "device": str(_DEVICE) if _DEVICE is not None else None,
115
+ "error": _LOAD_ERROR,
116
+ }
117
+
118
+
119
  def predict_change_mask(img1, img2, threshold=0.5):
120
  """
121
  Run AdaptFormer inference on two RGB numpy arrays (H, W, 3).
requirements.txt CHANGED
@@ -11,7 +11,8 @@ python-jose[cryptography]>=3.3.0
11
  passlib[bcrypt]>=1.7.4
12
  bcrypt==4.0.1
13
  pillow>=10.0.0
14
- numpy>=1.24.0
15
  opencv-python-headless>=4.8.0
16
  scikit-learn>=1.3.0
17
  requests>=2.28.0
 
 
11
  passlib[bcrypt]>=1.7.4
12
  bcrypt==4.0.1
13
  pillow>=10.0.0
14
+ numpy>=1.26,<2
15
  opencv-python-headless>=4.8.0
16
  scikit-learn>=1.3.0
17
  requests>=2.28.0
18
+ protobuf>=5.28.0,<6