supervision's metrics layer carries the highest risk — 5 functions to fix first

Five critical-band functions in supervision's metrics and VLM parsing layer show cyclomatic complexity between 38 and 58, all actively changing in the last 30 days, making them live regression risks rather than backlog items.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk18.71Low
Hottest Functionfrom_google_gemini_2_5

Antipatterns Detected

long_function6complex_branching5deeply_nested5exit_heavy5god_function5cyclic_hub1

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in supervision?

A god function is a single function that has absorbed so many responsibilities that it controls too much of the system on its own — high cyclomatic complexity, many exit paths, deep nesting, and calls to a large number of other functions all tend to appear together. In supervision, all five top hotspots are flagged as god functions, with cyclomatic complexity ranging from 38 to 58 and fan-out ranging from 20 to 29 distinct called functions. The concrete problem is blast radius: when a god function needs to change, the change touches many concerns simultaneously, making it hard to reason about what else might break and hard to write tests that cover the full behavior. In Python specifically, where type resolution happens at runtime, a fan-out of 29 implies more implicit coupling than a statically typed language would expose at compile time.

How do I reduce cyclomatic complexity in Python?

The most effective first step is the extract-method refactoring: identify a coherent sub-case within the function — a particular branch of a large conditional, or a self-contained processing stage — and move it into a named helper function with a clear signature. A cyclomatic complexity above 15 warrants splitting; above 30 it warrants immediate attention, because each independent path is a required test case. For `_compute` in `f1_score.py` (CC 46), the three top-level cases — both collections empty, targets absent with predictions present, and both present — map directly to three helper methods that could each be tested in isolation. That single split would likely bring the main method's complexity below 15 and make the remaining logic much easier to follow.

Is supervision actively maintained?

Yes, unambiguously. Every one of the top 5 hotspots is in the fire quadrant, meaning structurally complex and actively changing right now. `process_video` was touched 4 times in the last 30 days and last modified today. The three metrics `_compute` functions in f1_score.py, precision.py, and recall.py were each modified within the last 2 days, with 3, 2, and 2 commits in the last 30 days respectively. Even `from_google_gemini_2_5`, the most recently introduced of the five, was changed 8 days ago. With 472 of 1,037 functions in the fire quadrant and zero in the debt or ok quadrants, this is a project under active, broad development. High structural complexity and active maintenance are not mutually exclusive — supervision is a case where both are clearly true simultaneously.

How do I reproduce this analysis?

Clone the roboflow/supervision repository and check out commit `937ed4a` with `git checkout 937ed4a`. Then run `hotspots analyze . --mode snapshot --explain-patterns --force` using the Hotspots CLI, available at github.com/hotspots-dev/hotspots. The same command works on any local git repository without additional configuration — no `.hotspotsrc.json` is required to get started, though you can add one to exclude test fixtures or generated files from the results.

What does activity-weighted risk mean?

Activity-weighted risk combines structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — with recent commit frequency, so that functions which are both hard to understand and actively changing score the highest. A function with cyclomatic complexity 80 that hasn't been touched in two years scores much lower than one with cyclomatic complexity 20 touched every week, because the dormant function has lower near-term regression risk regardless of how complicated it looks. This prioritization focuses refactoring effort where it actually reduces the probability of a bug being introduced right now, rather than simply where the code looks complicated in the abstract. In supervision's case, the top-ranked function carries an activity-weighted risk score of 18.71 — structurally extreme and recently changed, making it a live concern rather than a cleanup backlog item.

Across 1,037 functions analyzed in roboflow/supervision at commit 937ed4a, 162 are in the critical band — and every one of the top 5 hotspots sits in the “fire” quadrant, meaning they are both structurally complex and actively changing right now. The top-ranked function, from_google_gemini_2_5, carries an activity-weighted risk score of 18.71 with a cyclomatic complexity of 58 and was last changed 8 days ago. I would start there, then move immediately to process_video, which was touched 4 times in the last 30 days and last modified today. supervision is a widely-used Python computer vision toolkit, and with 472 of 1,037 functions in the fire quadrant, the structural risk is not a backlog concern — it is a live shipping risk.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
from_google_gemini_2_5src/supervision/detection/vlm.py18.758629
process_videosrc/supervision/utils/video.py18.438624
_computesrc/supervision/metrics/f1_score.py17.746521
_computesrc/supervision/metrics/precision.py17.546520
_computesrc/supervision/metrics/recall.py17.339521

Large Repo Analysis

supervision is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 20 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

The shape of the risk

Before getting into individual functions, it’s worth seeing the full distribution. Every function in the repository falls into one of four quadrants based on structural complexity and recent commit activity.

Triage Band Distribution
Fire472Watch565

1,037 functions analyzed

There are no debt-quadrant or ok-quadrant functions in this repository at all — every structurally complex function is also actively changing. That is an unusual profile. It tells me that supervision is under heavy, broad development, and that structural complexity has accumulated in code that maintainers are actively shipping against. The watch-quadrant functions are moving but structurally simpler; the fire-quadrant functions are where both dimensions compound.

The antipattern distribution across the top 5 reinforces this picture:

Detected Antipatterns
Long Function×6Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Complex Branching×5Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Cyclic Hub×1Cyclic Hub
Participates in a call cycle with other high-traffic functions, creating circular dependency risk.

Every top hotspot is simultaneously long, branchy, deeply nested, exit-heavy, and god-function-sized. These are not independent problems — they tend to co-occur when a single function absorbs too many responsibilities over time.


Top 5 hotspots

from_google_gemini_2_5 — vlm.py

from_google_gemini_2_5
src/supervision/detection/vlm.py
18.71
critical
CC 58
ND 6
FO 29
touches/30d 1

This function parses and scales bounding boxes and segmentation masks from Google Gemini 2.5 JSON output. From the source excerpt I can see it handles the full input lifecycle: stripping markdown code fences from the raw string, JSON parsing with a fallback empty result on decode failure, type-checking the parsed structure, and then iterating over each detection item to validate fields, resolve class names, decode base64 mask images, and rescale box coordinates. That breadth is what drives the numbers.

A cyclomatic complexity of 58 means there are at least 58 independent execution paths through this function — each one a required test case and a potential bug surface. In Python, where duck typing means that type checks like isinstance(item, dict) are doing real runtime work rather than being caught at compile time, each branch is genuinely load-bearing. The max nesting depth of 6 means that at the deepest point, a reader is tracking six simultaneous layers of control flow — the item loop, field validation, class filtering, mask decoding, coordinate scaling, and edge-case guards all interleaving. Fan-out of 29 means 29 distinct functions are called, which in Python’s dynamic dispatch model implies more implicit coupling than that number alone suggests.

The god-function and exit-heavy patterns are visible directly in the excerpt: there are at least two early-return paths before the main loop even begins (one for JSON parse failure, one for non-list data), and the loop itself branches heavily per item. Each exit point is a separate test scenario.

Cyclomatic Complexity 58
threshold: 10

The file has only 1 commit in total and a single author in the last 90 days. There are no bug-linked commits or reverts, which is worth noting — the historical signal here is the novelty of the code rather than accumulated defects. This is a brand-new integration with Gemini 2.5, touched 8 days ago, and the complexity is all original design rather than accreted patches. That makes it a different kind of risk: not a function that has been broken and fixed repeatedly, but one that has never had the chance to be stress-tested.

Recommendation: Extract the JSON extraction and validation logic (fence-stripping, json.loads, type checks) into a dedicated _parse_gemini_json helper, and extract the per-item decoding logic (mask base64 decoding, box rescaling, class resolution) into a _decode_gemini_item function. That split alone would likely reduce the cyclomatic complexity of the main function below 20 and isolate the most failure-prone paths — JSON parsing and base64 mask decoding — into individually testable units.


process_video — video.py

process_video
src/supervision/utils/video.py
18.37
critical
CC 38
ND 6
FO 24
touches/30d 4

This is the most actively changing function in the top 5: 4 commits in the last 30 days, last modified today. The source excerpt confirms it orchestrates a three-stage threaded pipeline — a reader thread filling a bounded frame queue, a main-thread processor applying a user callback, and a writer thread draining a second bounded queue to disk. It also handles optional audio preservation via PyAV remuxing with a silent fallback on failure.

A cyclomatic complexity of 38 across that design reflects real control surface: queue sentinel management, max_frames capping, preserve_audio branching, show_progress tqdm integration, and error handling for the audio mux step, all layered inside thread orchestration code. The nesting depth of 6 reflects the interleaving of thread management with frame-level loop logic. Fan-out of 24 is broad for a utility function — it touches video I/O, queue management, threading primitives, progress display, and audio muxing, making it a genuine hub across the video subsystem.

The rate of review comments per commit is the highest of any function in the top 5, which tells me this function has attracted meaningful review attention even with only 4 total commits. That is consistent with the kind of function that reviewers instinctively scrutinize — threaded pipeline code with multiple failure modes is hard to review and hard to get right.

Cyclomatic Complexity 38
threshold: 10

Recommendation: The audio preservation path is self-contained enough to extract into a _mux_audio helper that takes source and target paths and returns a success/failure signal. The reader and writer thread bodies — currently likely defined as closures or nested functions — deserve promotion to named module-level functions with explicit signatures. Those two changes would reduce process_video to a coordinator function whose complexity derives primarily from pipeline wiring rather than implementation detail, and would make the threading behavior independently testable.


_compute — f1_score.py

_compute
src/supervision/metrics/f1_score.py
17.67
critical
CC 46
ND 5
FO 21
touches/30d 3

This method builds per-image statistics tuples — covering matches, ignored matches, confidence arrays, class IDs, and ground-truth class IDs — before delegating to class-level F1 aggregation. From the source excerpt, the branching structure is explicit: the function separately handles the case where both predictions and targets are empty (skip), targets are empty but predictions are present (all false positives), and targets are present (IoU matching). Within the targets-present branch, it further branches on whether predictions exist, what the metric target type is (boxes, masks, oriented bounding boxes), and whether required fields like class_id and confidence are present.

A cyclomatic complexity of 46 for a metrics computation method is high. Two of its three recent commits are tagged as fixes — a 67% fix rate — and it has 3 distinct authors active in the last 90 days, which is the highest author count of any function in the top 5. Multi-author ownership of a CC-46 function with a high fix fraction is a meaningful combination: it suggests different contributors are each encountering edge cases that require patching, which is the kind of pattern that accumulates when a function’s branching logic is difficult to reason about as a whole.

Bug Fix Fraction 67
threshold: 50

Recommendation: The three top-level cases (both empty, targets only, both present) map naturally to helper methods: _handle_fp_only_image, _handle_fn_only_image, and _handle_matched_image. Each would be independently testable against the IoU threshold grid, and the main _compute loop would reduce to a dispatcher. This mirrors the structure already implicit in the docstring.


_compute — precision.py

_compute
src/supervision/metrics/precision.py
17.49
critical
CC 46
ND 5
FO 20
touches/30d 2

The precision _compute method is structurally nearly identical to its F1 counterpart — same cyclomatic complexity of 46, same nesting depth of 5, fan-out of 20 versus 21, and the same three-case branching logic over the empty/present combinations of predictions and targets. The source excerpt confirms the parallel structure: the same size-category masking, the same false-positive handling branch, the same IoU-threshold linspace, and nearly identical field-validation raises.

This is the clearest signal of a shared abstraction that hasn’t been extracted. Two functions in the same metrics package with CC 46 and nearly identical shape, both actively changing (2 commits each in the last 30 days, last touched 2 days ago), and both with half their recent commits tagged as fixes, means that fixes applied to one are likely being manually mirrored to the other — a process that is itself error-prone. The 2 distinct authors active in the last 90 days and a review comment attached to roughly every commit suggest this file is getting real review attention, which is appropriate given the complexity.

Recommendation: The shared skeleton — size-mask construction, the three-case dispatch, and the IoU matching block — should live in a base class or shared module-level helper, parameterized on which metric is being accumulated. The _compute methods in f1_score.py, precision.py, and recall.py (see below) could then reduce to thin overrides that specify what gets accumulated into the stats tuple. That refactoring eliminates the synchronization burden between three parallel implementations.


_compute — recall.py

_compute
src/supervision/metrics/recall.py
17.3
critical
CC 39
ND 5
FO 21
touches/30d 2

The recall _compute completes the trio. At cyclomatic complexity 39 it is the structurally simplest of the three metrics implementations, but the source excerpt shows it handles the same IoU-threshold grid across boxes, masks, and oriented bounding boxes, with the same size-category masking logic and the same multi-branch empty/present dispatch. One difference visible in the excerpt is that the false-positive-only branch is absent — recall has no notion of false positives without ground truth, so that path simply doesn’t exist. That explains the slightly lower CC relative to the precision and F1 variants.

Like its siblings, this function was touched twice in the last 30 days and last modified 2 days ago, with half of its 2 recent commits tagged as fixes. The 2 distinct authors confirm the same multi-contributor dynamic. The fan-out of 21 includes calls to box_iou_batch, mask_iou_batch, and oriented_box_iou_batch — the IoU dispatch block is explicit in the excerpt and is present in all three metrics files, which is another instance of duplicated logic that would benefit from shared extraction.

Recommendation: In addition to the base-class refactoring described above, the IoU-dispatch block — the if self._metric_target == MetricTarget.BOXES / MASKS / ORIENTED_BOUNDING_BOXES chain — should be extracted into a shared _compute_iou(self, targets, predictions) method. It appears in identical form across all three files, and centralizing it means IoU-related bugs or new metric target types are fixed once rather than three times.


What the context functions tell me

The lower-priority functions surfaced alongside the top 5 reinforce where the active development pressure is concentrated. _rle_counts_to_mask in detection/utils/converters.py was touched 4 times in the last 30 days — the same frequency as process_video — and draw_text in draw/utils.py was touched 3 times. Both are in the watch quadrant: their structural complexity is low enough that they are not a refactoring priority today, but their activity level means they are worth monitoring as complexity accumulates. Similarly, sum in compact_mask.py has been touched twice recently, which tracks with the broader mask-handling work visible in the metrics layer.

None of these rise to the priority of the top 5, but they suggest the detection utilities and drawing subsystems are also in active flux.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
long_function6
complex_branching5
deeply_nested5
exit_heavy5
god_function5
cyclic_hub1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Reproduce This Analysis

git clone https://github.com/roboflow/supervision
cd supervision
git checkout 937ed4af37b0e77d3644dabbb60fcef2189c5413
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 20

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Related Analyses