diff --git a/client/platform/web-girder/api/configuration.service.ts b/client/platform/web-girder/api/configuration.service.ts index bb227fbee..2a0b07c74 100644 --- a/client/platform/web-girder/api/configuration.service.ts +++ b/client/platform/web-girder/api/configuration.service.ts @@ -30,7 +30,8 @@ export interface StatsResponse { jobs: { [jobType: string]: number; // Count of jobs by type }; - newUsers: number; // Total count of new users + newUsers: number; // Count of new users within the selected date range + totalUsers: number; // Total count of all users (not date-range filtered) }; groupByUser?: { datasets: { @@ -74,8 +75,29 @@ export interface AnnotationStatsReport { tracks: { total: number; confidenceGte1: number; + /** Sum of stored features over live tracks; absent in reports saved before this field existed. */ + detections?: number; + }; + /** Logical media totals; absent in reports saved before this field existed. */ + media?: { + /** One per video dataset / multicam camera — original + transcoded pair counts once. */ + videos: number; + imageSequences: number; + /** Individual image files across image-sequence and large-image datasets. */ + images: number; }; labels: AnnotationStatsLabelCount[]; + /** + * Distinct users who uploaded a dataset or modified/saved tracks in each + * window ending at `generatedAt`; absent in reports saved before this existed. + */ + activeUsers?: { + lastMonth: number; + last3Months: number; + last6Months: number; + lastYear: number; + last5Years: number; + }; } export interface AnnotationStatsJobResponse { diff --git a/client/platform/web-girder/views/Admin/AdminStats.vue b/client/platform/web-girder/views/Admin/AdminStats.vue index 18e9ebdc3..72974f0c5 100644 --- a/client/platform/web-girder/views/Admin/AdminStats.vue +++ b/client/platform/web-girder/views/Admin/AdminStats.vue @@ -42,6 +42,7 @@ export default defineComponent({ const annotationStatsJobId = ref(null); const annotationStatsError = ref(null); const annotationStatsReport = ref(null); + const showAnnotationStatsInfo = ref(false); const totalJobs = computed(() => { if (statsTableData.value) { @@ -257,6 +258,7 @@ export default defineComponent({ annotationStatsReport, runAnnotationStats, downloadAnnotationStats, + showAnnotationStatsInfo, }; }, }); @@ -304,6 +306,11 @@ export default defineComponent({ Datasets: {{ statsTableData?.datasets }} +
+ + Total Users: {{ statsTableData?.totalUsers }} + +
New Users: {{ statsTableData?.newUsers }} @@ -429,12 +436,34 @@ export default defineComponent({ - Annotation inventory + + Annotation inventory + + + + How these stats are calculated + +

- Run a background job that counts datasets, tracks, confidence ≥ 1, + Run a background job that counts datasets, tracks, detections, + videos, image sequences and individual images, confidence ≥ 1, curation classes (mostly manual / substantially corrected / mostly computed), - and label totals. Results are saved under your user + label totals, and active users (uploaded a dataset or modified/saved + tracks) over the last month, 3 months, 6 months, year and 5 years. + Results are saved under your user Stats folder so they remain after refresh.

Generate annotation report + + + mdi-information-outline + + How are these calculated? + Datasets: {{ annotationStatsReport.datasets.total }}
+ Tracks: {{ annotationStatsReport.tracks.total }} + + Detections: {{ annotationStatsReport.tracks.detections }} + Confidence ≥ 1: {{ annotationStatsReport.tracks.confidenceGte1 }} @@ -500,6 +558,27 @@ export default defineComponent({ Empty: {{ annotationStatsReport.datasets.empty }} +
+ Active users: + + Last month: {{ annotationStatsReport.activeUsers.lastMonth }} + + + Last 3 months: {{ annotationStatsReport.activeUsers.last3Months }} + + + Last 6 months: {{ annotationStatsReport.activeUsers.last6Months }} + + + Last year: {{ annotationStatsReport.activeUsers.lastYear }} + + + Last 5 years: {{ annotationStatsReport.activeUsers.last5Years }} + +
+ + + + + + mdi-information-outline + + How annotation stats are calculated + + + mdi-close + + + + + + This is a point-in-time inventory of every dataset folder, counting only + live tracks (deleted revisions are excluded). Generating a + report re-runs the counts and saves the result to your Stats + folder; the numbers shown are from the report’s + generatedAt timestamp, not a live query. + + +

Manual vs. corrected — the key difference

+

+ These two signals describe different things and are counted from different + fields, so a track is never counted as both: +

+ + + +
+ + mdi-pencil-plus-outline + + Manual — created by a person +
+

+ The track was originally drawn / created by a user in + the annotator. It is identified directly from provenance: + attributes.userCreated === true. +

+

+ A manual track stays “manual” no matter how much it is + edited afterwards, and regardless of its confidence value. +

+
+
+ + +
+ + mdi-robot-outline + + Corrected — computed, then endorsed +
+

+ The track was created by a pipeline / import + (userCreated is not true), but its first + confidence pair is now ≥ 1 — the state left + behind when a reviewer accepts the track or sets its type, which + stores a single pair at confidence 1. +

+

+ Unlike “manual”, this is an inferred proxy for + human correction, not a recorded edit history (see limitations below). +

+
+
+
+

+ In short: manual = a human made the track; + corrected = a machine made the track and a human (probably) + signed off on it. A dataset can contain a mixture of both. +

+ +

Per-track signals

+

+ For each dataset the job tallies its live tracks into the counters below. + The classification that follows is built entirely from these four numbers. +

+ + + + +

Dataset classification

+

+ Each dataset with at least one live track is placed in exactly one + category. Rules are evaluated in this order — + “mostly manual” wins if a dataset somehow matches both rules: +

+ + + +

+ Worked examples +

+ + + + +

Totals and labels

+
    +
  • + Datasets counts every dataset folder, including empty + ones; the four category chips always sum to this total. +
  • +
  • + Videos counts one logical video per video dataset + (and per camera of a multicam dataset), from the folder’s + meta.type — not per file. A folder holding + both an original video and its transcoded + *.transcoded.mp4 derivative therefore counts once, as + does a web-safe original whose transcoding was skipped. +
  • +
  • + Image sequences counts one per image-sequence dataset + (and per camera of a multicam dataset), also from + meta.type. The multicam parent folder itself counts as + neither a video nor an image sequence. +
  • +
  • + Images counts the individual image files in + image-sequence and large-image datasets, in a single grouped pass + over those folders’ items (all valid image / large-image + extensions, so an original counts once and its converted + replacement — which deletes the original — also counts + once). A cloned dataset’s images are resolved through its + source folder and count toward the clone, just as its copied + tracks do. Large-image files count as images but their datasets + are not image sequences. +
  • +
  • + Tracks counts live tracks across all datasets. +
  • +
  • + Detections is the total number of stored features + across those live tracks — one feature is one detection on + one frame. +
  • +
  • + Labels (in the downloaded JSON) group live tracks by + the type name of their highest-scoring confidence pair. +
  • +
+ +

Active users

+

+ A user is active in a window if, during that window, they did + either of the following. Each user is counted once per window, even if they + did both (or did one many times): +

+
    +
  • + Uploaded a dataset — created an annotate dataset + folder (the folder’s creatorId / created). +
  • +
  • + Modified / saved a track — authored a revision-log + entry. Saving annotations writes one entry with the saving user and the + save time, and it is the only record tying a user and a time to a track + modification, so imports and clone initialization (which use the same + save path) count as well, while rolled-back saves no longer do. +
  • +
+

+ The five windows — last month, 3 months, 6 months, year and 5 years + (30 / 90 / 180 / 365 / 1825 days, matching the approximations used by the + stats above) — all end at the report’s generatedAt + time and are nested, so the counts can only grow as the window widens. + Because of that nesting, only each user’s most recent qualifying + activity is needed to place them in every window. Reports saved before + this was added have no active-user counts until they are regenerated. +

+ +

Limitations — read the corrected numbers with care

+
    +
  • + Corrected is inferred, not audited. The job deliberately + does not scan per-detection edit history (userModified), + which keeps it fast on very large collections but means the proxy can be + wrong in both directions. +
  • +
  • + Possible false positives: a pipeline or import that + already emits confidence 1.0 counts as + “corrected” even if nobody ever reviewed it. +
  • +
  • + Possible false negatives: geometry-only edits, + deletions, attribute edits, type changes left below confidence + 1, and corrections that are not the first confidence pair + are invisible to this count. +
  • +
  • + Thresholds are heuristics. A dataset just under a cutoff + (e.g. 14 corrected tracks, or 24% corrected) lands in + “mostly computed”, so small datasets in particular can sit in + a category that understates the human effort involved. +
  • +
+
+ + + + + Close + + +
+