Repository navigation
Add the Anomaly Detection Model and Anomaly Heatmap Visualization workflow blocks - #3201
Conversation
ModelsProvider.run_classification gains include_anomaly_map; the server adapter sets it on ClassificationInferenceRequest only when asked, so the request a classification block builds is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
roboflow_core/anomaly_detection_model@v1 runs a PatchCore or FoundAD model and outputs predictions, anomaly_score, anomaly_threshold, is_anomalous and the float32 anomaly_map at network resolution. An anomaly_threshold input overrides the saved threshold. The tensor sibling carries the verdict in images_metadata and serialise_native_classification writes it through. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
roboflow_core/anomaly_heatmap_visualization@v1 stretches an anomaly map onto its image and blends it on a scale anchored to the threshold, with the Roboflow app's ramp or an OpenCV colour map. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
👋 Thanks for the pull request! Here is how automated Claude review works here, so you spend credits (and reviewer time) wisely. 🚦 This PR is marked Ready for review, so automated Claude review will run — and every pass spends real credits. Warning 💸 The Claude reviewer bills in credits, not vibesAutomated review spins up a real agent that reads real code and spends real credits on every pass. It is glad to help — but it is not a rubber duck, a linter you poke in a loop, or a substitute for reading the contributing guide. Treat it like an expensive senior reviewer whose time you booked, and show up prepared. Draft when unsure, Ready when you mean it:
However you get there, arrive prepared:
Reviews are not free. A draft costs nothing to review; a Ready PR is a promise that it is worth reviewing.
|
|
🤖 Claude review started at commit New commits are not auto-reviewed. Add the |
|
@probicheaux — please answer the question below. ⏳ This PR is on hold pending your answers — the review will not advance to sign-off until the IMPORTANT questions below are answered. IMPORTANT questions
Re-review: new commits are NOT auto-reviewed. Add the Reviewed at HEAD: 18bc374 |
|
Claude review summary Skills: review-workflows-blocks, review-core-infra, review-topic-backward-compat-and-versioning, review-topic-local-vs-remote-execution, review-topic-prediction-integrity, review-topic-test-hygiene No high-confidence blocking findings at this commit. This is not a sign-off: the open IMPORTANT question above has to be answered first. Traced and verified:
Tests to add (non-blocking):
Minor doubts (non-blocking): the threshold override accepts zero or negative values, which the heatmap block then rejects at runtime. Anomaly models only load under the Commands that informed the review: Reviewed at HEAD: 18bc374 |
|
Release coordination
Reviewed at HEAD: 18bc374 |
The anomaly detection block passes `include_anomaly_map` to `InferenceConfiguration` on its remote path. The field shipped in inference-sdk 1.6.1 and 1.7.2 but not in 1.7.0 or 1.7.1, whose frozen dataclass rejects the keyword, so the floor moves to the release that has it (the lock already resolves 1.7.2). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Answer to question 1. |
|
🤖 Claude review started at commit New commits are not auto-reviewed. Add the |
| "torchvision", | ||
| "inference-models>=0.37.1", | ||
| "inference-sdk>=1.7.0", | ||
| "inference-sdk>=1.7.2", |
There was a problem hiding this comment.
High — workflows/uv.lock is now stale, so uv lock --check will fail.
The floor moved to >=1.7.2, but workflows/uv.lock was not regenerated. It still records the root package's requirement as { name = "inference-sdk", specifier = ">=1.7.0" } (workflows/uv.lock:4152). uv lock --check compares the lock's recorded requires-dist against pyproject.toml and fails when they differ. Resolving 1.7.2 doesn't help, because the specifier itself doesn't match.
make create_workflows_wheel runs cd workflows && uv lock --check (Makefile:47) before uv build. create_isolation_wheels depends on that target. The target is used by the unit/integration workflow jobs, unit_tests_inference_x86, unit_tests_inference_cli_x86, integration_e2e_tests_inference_sdk_x86 and publish.pypi.roboflow_workflows.yml. As a result, all of those jobs and the package publish fail at the wheel-build step.
Fix: run cd workflows && uv lock and commit the updated uv.lock. The only expected change is that specifier line, since 1.7.2 is already the resolved version.
There was a problem hiding this comment.
Fixed in 3e57b3b: workflows/uv.lock now records inference-sdk>=1.7.2 (the locked version was already 1.7.2, so only the requires-dist line changed). uv lock --check passes with uv 0.11.31, the version CI pins.
|
Claude review summary (re-run) Skills: review-workflows-blocks, review-topic-backward-compat-and-versioning, review-packaging-ci, review-topic-test-hygiene Delta since Question 1 (SDK version with Blocking (1 open):
The non-blocking test suggestion from the previous summary still applies: an end-to-end workflow integration test chaining the model block into the heatmap block under both This is not a sign-off. Fix the lockfile and add the Commands that informed the review: Reviewed at HEAD: 46c79d0 |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
🤖 Claude review started at commit New commits are not auto-reviewed. Add the |
|
Claude review summary (re-run) Skills: review-packaging-ci, review-topic-backward-compat-and-versioning Delta since Blocker resolved: the stale Question 1 (the SDK version that ships Commands that informed the review: Reviewed at HEAD: 3e57b3b |
|
😎 PR passes the vibe-check and trust-me-bro verification. |
|
Maintainer review requested. Final approval and merge remain in GitHub. |
PawelPeczek-Roboflow
left a comment
There was a problem hiding this comment.
Hi there.
Code itself looks fine - I need integration test for workflows suite and E2E test against the platform on staging + prod. We need models in the reference projects we use for all tests, such that injected secrets match.
…w-block # Conflicts: # workflows/pyproject.toml # workflows/uv.lock
PatchCore and FoundAD now call an image anomalous when its anomalous confidence, sigmoid((score - threshold) / scale), reaches the request confidence instead of always deciding at the saved threshold. The models take recommended_parameters, so "best" resolves to the model-eval value and "default" to 0.5, which reproduces the calibrated decision. A classification request that omits confidence (body or legacy query) gets "best" for anomaly models; plain classifiers keep their 0.4 default. The response carries the calibration scale as anomaly_scale so callers can re-decide against their own threshold with the same rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The block sends confidence (default 0.5) to the server, which decides against the saved threshold. With an anomaly_threshold override the block re-decides with the same rule relative to the override, recomputing both class confidences from the served anomaly_scale, so the two inputs compose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The inference_server legacy routes and translation now leave an omitted classification confidence unset, so anomaly models resolve it to the model-eval value instead of the 0.4 request default; the anomaly response and the typed serializers carry anomaly_scale. The classification action declares its confidence param, and the workflows provider accepts include_anomaly_map like the protocol requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Anomaly Detection Model block outputs anomaly_decision_threshold, the raw score at which the anomalous confidence meets its confidence, and the heatmap docs point its anomaly_threshold input at it, so the middle of the ramp marks the verdict at any confidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workflow integration test chains the Anomaly Detection Model block into the heatmap for the public FoundAD and PatchCore models of the anomaly reference project, under both tensor representations: the verdict on a clearly anomalous and a clearly normal image, confidence moving it, and the threshold override composing with confidence. The hosted platform tests run the same models on staging and prod through API v0 and v1, checking the verdict rule against the served score, threshold and scale. A decision threshold at a very low confidence can reach zero, so the heatmap now saturates past a non-positive anchor, the limit of its ramp, instead of rejecting it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The below is claude generated
|
PatchCore and FoundAD are registered only under USE_INFERENCE_MODELS; the legacy loader falls back to getWeights, which serves a workspace-qualified model only to its own workspace, so the test cannot run on that leg. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Retested e2e with latest changes, everything looking good! |
A 640px PatchCore model has 6400 patches per image and a memory bank of ~140k entries, so a 4096-row chunk was a 2.13 GiB distance matrix plus temporaries per request. Concurrent model-eval requests ran the GPU out of memory and the server answered 500. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PawelPeczek-Roboflow
left a comment
There was a problem hiding this comment.
LGTM, fixed minor issues and prepared for release
Description
Workflows had no way to run a PatchCore/FoundAD anomaly model and show its heatmap: the classification block drops
anomaly_map, and the existing Heatmap Visualization block is built for detections (customer ask from Flex via #discuss-models; remaining GA item on MODELS-1194). Standard blocks, no new kinds, no new dependencies (as discussed in #discuss-inference-release).This PR also makes the request
confidencedecide an anomaly model's verdict, so a caller can tune the operating point the same way as for any other model.Anomaly confidence contract (inference_models + both servers)
An anomaly model's anomalous confidence is
sigmoid((score - threshold) / scale), with the threshold and scale calibrated at training, so it is exactly 0.5 at the saved threshold. The verdict is nowis_anomalous = anomalous_confidence >= confidenceinstead of alwaysscore >= threshold.confidenceresolves like the other models: a number in[0, 1],"best"(the model-eval recommendation, viarecommended_parameters, which PatchCore/FoundAD now accept), or"default"(0.5, the calibrated decision).confidencegets"best"for anomaly models (the request field defaults to 0.4, which would move the decision). The v1 body route checksmodel_fields_set; the legacy/{dataset}/{version}route now leavesconfidenceunset for classification when the query omits it. Plain classifiers keep their 0.4 default.anomaly_scale.anomaly_thresholdstays the saved threshold, so a caller can re-decide against their own threshold with the same rule.inference_server/inference_model_managerstack applies the same contract:build_task_paramsforwards a classificationconfidenceonly when the caller sent one, the legacy router mirrors the legacy server,AnomalyDetectionResponseand the typed serializers carryanomaly_scale, the classification action declaresconfidence, and the workflows provider'srun_classificationacceptsinclude_anomaly_maplike the protocol.The companion platform change registers 0.5 as the recommended confidence for anomaly evals (roboflow/roboflow#18033), so
"best"reproduces the calibrated decision.PatchCore memory under concurrent requests
PatchCore scored patches in 4096-row chunks against the whole memory bank. A 640px model has 6400 patches per image and a ~140k-entry bank, so each chunk was a 2.13 GiB distance matrix plus temporaries, per request. Model eval's 16 concurrent requests ran the serverless GPU out of memory and got
500 Internal error, failing the PatchCore evals forpaul-guerrie-tang1/anomaly-det-algbq-1-patchcore-t2andpaul-guerrie/anomaly-det-1-patchcore-t2. The chunk is now bounded by elements (2^26, 256 MiB in fp32) and the distances are computed in place withaddmm, so one chunk-sized buffer is live. Reproduced on an L4 with a cold server (16 concurrent first requests: 16× 500, CUDA OOM inscore_patches); with the fix, 16× 200 and identical scores and verdicts.roboflow_core/anomaly_detection_model@v1Runs an anomaly model (served on the classification route, see #3007/#3144) and outputs:
predictionsclassification_predictionanomaly_score,anomaly_threshold,anomaly_scale,is_anomalous; never carries the raw mapanomaly_scorefloatanomaly_thresholdfloatanomaly_decision_thresholdfloatconfidence:anomaly_threshold + scale * logit(confidence),anomaly_thresholditself at 0.5is_anomalousbooleanconfidenceanomaly_mapnumpy_array(h, w)at the network input resolution (square),Nonewheninclude_anomaly_mapis offinference_id,model_idInputs:
images,model_id,include_anomaly_map(defaultTrue),confidence(default 0.5, sent to the server),anomaly_threshold(optional raw-score override),disable_active_learning,active_learning_target_dataset. With an override the block re-decides with the same rule relative to it, recomputing both class confidences fromanomaly_scale, so the two inputs compose. A plain classifier fed to the block raises with a message pointing at the classification block. LOCAL goes throughModelsProvider.run_classification(..., include_anomaly_map=...)(set on the request only whenTrueso the request-shape parity test keeps passing); REMOTE throughInferenceConfiguration(include_anomaly_map=..., confidence_threshold=...), accepting both the SDK-decoded ndarray and the payload dict. The tensor sibling emits a nativeClassificationPredictionwith the anomaly fields inimages_metadata[0];serialise_native_classificationpasses them through with the decided class first. Output kind order is identical on both siblings.roboflow_core/anomaly_heatmap_visualization@v1Stretches the map onto its image (bilinear) and blends it on a scale anchored to
anomaly_threshold,t = max(v, 0) / (max(v, 0) + anchor). Connected to the model block'sanomaly_decision_threshold, 0.5 marks the block's verdict at any confidence. A very low confidence can put the decision at or below zero; the ramp then saturates every positive value, the limit of the formula, instead of rejecting it.color_mapisROBOFLOW(the ramp the Roboflow app draws in its inference preview, with its alpha stops) or an OpenCV map (JET,TURBO,INFERNO,VIRIDIS,HOT);opacitydefault 0.75;copy_imageas the other visualizations. A missing map (model block run withinclude_anomaly_map=False) raises rather than passing the image through.Shared helpers:
core_steps/common/anomaly_detection.py(payload decoder duplicated frominference_sdk, since the workflows package cannot importinference.core; the override and decision-threshold rules),visualizations/common/annotators/anomaly_heatmap.py(ramp + annotator, shared by both viz siblings). Both blocks are registered in bothloader.pybranches;workflows/CHANGELOG.mdhas the entries under Unreleased → Added. Block docs are generated from the manifests.Reference models for the tests
FoundAD (10 epochs) and PatchCore trained on the same anomaly dataset in the reference workspaces whose keys CI already injects:
paul-guerrie/anomaly-det(staging,LOAD_TEST_STAGING_API_KEY) andpaul-guerrie-tang1/anomaly-det-algbq(prod,LOAD_TEST_PRODUCTION_API_KEY). The prod project is public on Universe, so the workflow integration suite reaches it withAPI_KEYlike its other public models. Test images: one clearly anomalous and one clearly normal image from that dataset (tests/workflows/integration_tests/execution/assets/anomaly_*.jpg), each at least 20% from the threshold on all four models.Type of change
How has this change been tested, please provide a testcase or example of how you tested the change?
tests/workflows/integration_tests/execution/test_workflow_with_anomaly_detection.py): the model block into the heatmap against the public FoundAD and PatchCore models, run locally with a key from a different workspace, underENABLE_TENSOR_DATA_REPRESENTATION=FalseandTrue: 6 passed each. Covers the verdict on both images, confidence 0/1 flipping it, and the override composing with confidence.tests/inference/hosted_platform_tests/test_anomaly_detection_models.py, models registered inMODELS_TO_BE_USEDfor staging and prod): verdicts at the default confidence, the served class confidence matchingsigmoid((score - threshold) / scale), a request confidence flipping the verdict without moving the threshold, and the map at network resolution, through API v0 and v1 and both auth modes. Run against a local server from this branch with the prod load-test key (HOSTED_PLATFORM_TESTS_PROJECT=roboflow-platform-localhost): 20 passed. Against hosted staging/prod they pass once this ships, since the deployed servers do not yet serveanomaly_scale.core_steps/common, anomaly blocks and visualizations 1243 passed (the 27 errors are font-download tests that cannot reach GitHub from my machine);inference_serverunit tests 9961 passed;inference_model_managerunit tests 956 passed.Any specific deployment considerations
anomaly_scale, which the anomaly models now put in their metadata.confidenceof 1 or more is divided by 100 (existing behavior), soconfidence=1.0there means 0.01.Docs
development/docs/build_block_docs.py).🤖 Generated with Claude Code