Files

62 lines
2.3 KiB
Python

from __future__ import annotations
import io
from pathlib import Path
import joblib
import numpy as np
from flask import Flask, jsonify, request, send_from_directory
from petri_cv import candidate_points, features_for_points, prepare_image
ROOT = Path(__file__).resolve().parent
MODEL_PATH = ROOT / "models/petri_candidate_classifier.joblib"
app = Flask(__name__)
model_bundle = joblib.load(MODEL_PATH) if MODEL_PATH.exists() else None
@app.get("/")
def index():
return send_from_directory(ROOT, "index.html")
@app.get("/<path:path>")
def assets(path: str):
return send_from_directory(ROOT, path)
@app.post("/api/analyze")
def analyze():
if model_bundle is None:
return jsonify(error="Модель не обучена. Запустите train_petri_model.py."), 503
uploaded = request.files.get("image")
if uploaded is None or not uploaded.mimetype.startswith("image/"):
return jsonify(error="Передайте изображение PNG, JPG или WEBP."), 400
try:
prepared = prepare_image(io.BytesIO(uploaded.read()))
except Exception:
return jsonify(error="Не удалось прочитать изображение."), 400
points = candidate_points(prepared)
if not points:
return jsonify(colonies=[], candidates=0, model="RandomForest candidate ranker")
probabilities = model_bundle["model"].predict_proba(features_for_points(prepared, points))[:, 1]
ranked = sorted(zip(probabilities, points), reverse=True)
selected = []
# Non-maximum suppression avoids several markers on a single colony.
for score, (x, y) in ranked:
if score < .54:
continue
if all((x - candidate["x_px"]) ** 2 + (y - candidate["y_px"]) ** 2 > 36 ** 2 for candidate in selected):
selected.append({"x": round(x / prepared.gray.shape[1], 5), "y": round(y / prepared.gray.shape[0], 5),
"x_px": x, "y_px": y, "score": round(float(score), 3)})
if len(selected) >= 300:
break
for item in selected:
item.pop("x_px")
item.pop("y_px")
return jsonify(colonies=selected, candidates=len(points), model="RandomForest candidate ranker")
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, debug=False)