Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/real-yolo-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
contents: read

jobs:
wheat-sample:
all-crop-samples:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand All @@ -27,5 +27,5 @@ jobs:
run: python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
- name: Install Ultralytics
run: python -m pip install ultralytics
- name: Run a real sample prediction on CPU
- name: Run real crop checkpoints on CPU
run: python inference/tests/smoke_real_model.py
4 changes: 2 additions & 2 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
| Vue build | `cd frontend && npm ci && npm run build` | The frontend bundle compiles | Browser integration or API responses |
| Spring Boot checks | `cd backend && mvn -Dtest=DeepSeekControllerTest,FileControllerTest package` | Backend compilation, selected input/control tests | Database integration or paid AI connectivity |
| Documentation links | `python scripts/check_docs_links.py` | Relative Markdown links resolve | External link availability |
| **Real YOLO smoke test** | GitHub Actions: `Real YOLO CPU smoke test` | Loads an actual wheat checkpoint and predicts one bundled JPG on a GitHub-hosted CPU | Diagnostic accuracy, full application workflow, multi-user concurrency, camera/video streaming, other weights |
| **Real YOLO smoke test** | GitHub Actions: `Real YOLO CPU smoke test` | Loads all nine crop checkpoints and predicts one corresponding bundled JPG per crop on a GitHub-hosted CPU | Diagnostic accuracy, full application workflow, multi-user concurrency, camera/video streaming, disease classification accuracy |

## Run the real inference smoke test

Expand All @@ -19,7 +19,7 @@ gh run list -R FuTseYi/CropLens --workflow real-yolo-smoke.yml --limit 1

Alternatively, use **Actions → Real YOLO CPU smoke test → Run workflow** in the GitHub repository.

The optional test installs CPU PyTorch/Ultralytics packages and may download substantial dependencies. It is intentionally not run on every PR or on ordinary documentation updates. Read the run logs for the actual pass/fail status. A passing smoke test indicates only that **one checkpoint can load and process one repository sample**; it says nothing about inference accuracy or deployment readiness.
The optional test covers nine weight files and matching sample folders. It installs CPU PyTorch/Ultralytics packages and may download substantial dependencies. It is intentionally not run on every PR or on ordinary documentation updates. Read the run logs for the actual pass/fail status. A passing smoke test indicates only that **each tested checkpoint can load and process one matching repository sample**; it says nothing about inference accuracy or deployment readiness.

## Before claiming production readiness

Expand Down
73 changes: 45 additions & 28 deletions inference/tests/smoke_real_model.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,56 @@
"""Execute one real CPU-only crop model prediction without Flask or a database.
"""CPU-only smoke test: load and run each bundled crop model on one real JPG.

Unlike the standard mock-based unit tests, this must load a checked-in
Ultralytics checkpoint and decode an actual sample image. It does not
evaluate diagnostic accuracy or full frontend/backend integration.
No Flask, database or browser is needed. This checks basic compatibility,
not prediction accuracy, safety or reliable crop-disease diagnosis.
"""
import gc
from pathlib import Path

from ultralytics import YOLO


CROPS = (
("apple", "苹果"),
("corn", "玉米"),
("cotton", "棉花"),
("grape", "葡萄"),
("potato", "马铃薯"),
("rice", "水稻"),
("strawberry", "草莓"),
("tomato", "番茄"),
("wheat", "小麦"),
)


def main():
root = Path(__file__).resolve().parents[2]
weights = root / "inference" / "weights" / "wheat_best.pt"
images = sorted((root / "assets" / "samples" / "小麦").glob("*.jpg"))
if not weights.is_file() or not images:
raise SystemExit("Required wheat checkpoint/sample not found")
image = images[0]

print("Model:", weights.relative_to(root))
print("Image:", image.relative_to(root))
model = YOLO(str(weights))
results = model.predict(
source=str(image),
device="cpu",
imgsz=640,
conf=0.25,
save=False,
verbose=False,
)
assert len(results) == 1, "Expected exactly one image prediction"
result = results[0]
assert result.boxes is not None, "Expected a detection result structure"
assert len(result.orig_shape) == 2 and all(n > 0 for n in result.orig_shape)
print("Real inference succeeded; image shape:", result.orig_shape)
print("Detections:", len(result.boxes))
print("This is a smoke test, not an accuracy or clinical evaluation.")
tested = 0
for crop, chinese_folder in CROPS:
weights = root / "inference" / "weights" / (crop + "_best.pt")
samples = sorted((root / "assets" / "samples" / chinese_folder).glob("*.jpg"))
if not weights.is_file() or not samples:
raise SystemExit("Missing checkpoint or sample for {}".format(crop))

image = samples[0]
print("Checking {} with {} ...".format(crop, image.relative_to(root)), flush=True)
model = YOLO(str(weights))
results = model.predict(
source=str(image), device="cpu", imgsz=640,
conf=0.25, save=False, verbose=False,
)
assert len(results) == 1, "Expected one image result for {}".format(crop)
result = results[0]
assert result.boxes is not None, "Missing detection result for {}".format(crop)
assert len(result.orig_shape) == 2 and all(n > 0 for n in result.orig_shape)
print("OK: {} — image {} — {} detections".format(
crop, result.orig_shape, len(result.boxes)), flush=True)
tested += 1
del result, results, model
gc.collect()

assert tested == len(CROPS)
print("All {} crop checkpoints completed CPU inference.".format(tested))
print("This is a smoke test only, NOT a validation of model accuracy.")


if __name__ == "__main__":
Expand Down
Loading