Skip to content

Releases: PlainsightAI/openfilter

Release v1.1.2

Choose a tag to compare

@plainsight-bot plainsight-bot released this 29 Jun 01:20
c95c0ac

Fixed

  • GPU usage percent emitted as float for Cloud Monitoring (FILTER-585): Metrics.gpu_thread_func now casts gpu_usage_percent to float before export. NVML reports gpu_util as an integer, but the Cloud Monitoring descriptor openfilter_gpu_usage_percent is type-locked to DOUBLE, so each export batch had one point rejected with value type for metric must be DOUBLE, but is INT64. The cast is targeted to this single summary field; the per-device gpuN series and the legitimately INT64 descriptors (gpu_accessible, camera_connected) are unchanged.

Release v1.1.1

Choose a tag to compare

@plainsight-bot plainsight-bot released this 01 Jun 20:00
d84239f

Fixed

  • Multi-topic Subject Data Streaming support for Webvis: Webvis now maintains isolated frame subject metadata per-topic, exposing them individually at /{topic}/data while providing a combined overview of all topic data at /data or /main/data for multi-topic pipelines. Backwards compatibility for single-topic pipelines is fully preserved. Added cheap concurrency snapshots of current data and reordered FastAPI routes to ensure that /data requests are not incorrectly matched by the /topic wildcard image endpoint.

Release v1.1.0

Choose a tag to compare

@plainsight-bot plainsight-bot released this 21 May 19:59
8f33a17

Added

  • OpenTelemetry MQ hop spans + per-frame trace propagation (PLAT-866, #99): Every filter-to-filter hop now produces mq.send / mq.recv outer hop spans (attributes: topic, payload_bytes, frame.id, frame.format, mq.id), with nested frame.serialize / frame.deserialize CPU sub-spans and zmq.send_multipart / zmq.recv_multipart kernel-syscall sub-spans. frame.encode_jpg / frame.decode_jpg codec sub-spans fire on the jpg transport path only. Per-frame W3C trace context (traceparent / tracestate) now travels inside the ZMQ envelope via the standard opentelemetry.propagate.inject / extract (TraceContextTextMapPropagator); the consumer-side {FilterClass}.process span (PLAT-848) parents on the per-frame context extracted from the envelope, so filter A's frame-17 spans and filter B's frame-17 spans share a single trace ID and nest correctly. The TRACEPARENT env-var fallback (PLAT-848 behavior) is retained for source filters that never call mq.recv (e.g. VideoIn). When tracing is disabled (the default, TELEMETRY_EXPORTER_TYPE=silent), the hot path short-circuits cleanly: codec / kernel sub-spans bail on a single is_recording() check before any tracer lookup or Span allocation, and the per-recv time_ns() brackets + per-part byte tally in ZMQReceiver.recv_once / ZMQSender.send_maybe are gated on the module-level hop tracer being set (so they don't run either). No new environment variables.

  • Windowed async batch dispatch for process_batch() (FILTER-457, FILTER-458, FILTER-459, FILTER-460, #95): four composable, opt-in additions to the runtime batch path. process_batch() result slots may now be deferred Callables that mq.send resolves lazily, so a filter can hand batch work to a worker thread instead of blocking the loop thread on the batch's wall-clock duration. A new accumulate_window config decouples the runtime ring buffer from batch_size, with a public Filter.select_batch() hook to pick which frames are dispatched. Filter.flush_batch() plus batch_trigger: "auto" | "manual" give filters explicit control over when a batch drains. A batch_workers pool runs process_batch() on a ThreadPoolExecutor with semaphore backpressure, so the loop accumulates the next batch while previous batches are still in flight. All four preserve existing behavior when unset (batch_workers=1, batch_trigger="auto", accumulate_window unset).

Release v1.0.0

Choose a tag to compare

@plainsight-bot plainsight-bot released this 19 May 17:07
85e17c3

A new foundation for production Vision AI. OpenFilter 1.0 transitions the runtime from an evolving framework into a stable, production-grade platform. The new typed configuration surface is purely additive — the legacy dict-based FilterConfig coexists alongside it, so unmigrated filters keep working through the runtime fallback. Behavioral changes that do require filter-side updates are scoped to the items called out under ### Breaking Changes below.

Declarative Configuration Foundations

Each filter can now publish a build-time JSON Schema artifact alongside the runtime validation OpenFilter has always done — consumers can validate, document, and render configuration UIs against a filter without ever running it.

  • Typed FilterConfigBase with emit_schema() (FILTER-441, #86): filters can declare their config surface as a pydantic model and emit it as JSON Schema (draft 2020-12) for build-time consumption. Managed / Resolve helpers mark fields as orchestrator-controlled or platform-resolved.
  • FilterOutputSchema + frame.data shape catalog (FILTER-444, #88): declarative frame.data shape declaration. The catalog at openfilter.filter_runtime.shapes ships canonical types — BoundingBox, Polygon, Mask, Keypoint, Detection, DetectionSet, Track, TrackSet, Pose, PoseSet, KeypointSet, OCRSpan, OCRSpanSet, ClassificationResult — with stable $ids under https://schemas.plainsight.ai/shapes/<kebab>/v1. Filters reference shapes via $ref instead of negotiating dialects out-of-band.
  • openfilter emit-schema CLI (FILTER-442, #87): emits a filter's JSON Schema to stdout or -o <path>. Auto-detects the canonical class in a module or accepts module:Class to disambiguate.

Reference migrations ship in filter-template and filter-sam3-detector; every other filter in the library keeps working unchanged through the runtime fallback.

Backward-Compatible Runtime Improvements

Two runtime improvements teams have been asking for, landed underneath the compatibility surface:

  • Zero-copy shared-memory transport between co-located filters (#82) — eliminates inter-stage serialization on the same host.
  • Batched inference via process_batch() and batch_size > 1 (#61, v0.1.29) — meaningful throughput gains for SAM3, YOLO, and transformer-OCR filters that benefit from batched forward passes.

Production-Grade Engineering

  • First-class observability: per-filter OpenTelemetry distributed tracing (#79), OpenLineage events (v0.1.5), per-frame timing metrics, and a turn-key Grafana stack (v0.1.21–22). Wall-clock latency dashboards separate process() time from ZMQ and queue overhead.
  • Supply-chain hygiene: SLSA provenance and SBOM attestations on every Docker image; token auth and CORS for HTTP-exposed filters (v0.1.30).
  • GPU and deployment ergonomics: framework-agnostic GPU detection via ctypes (v0.1.28); GKE-compatible LD_LIBRARY_PATH injection (v0.1.26), so CUDA-dependent images deploy without per-cluster fixups.
  • Filter library refresh: new ImageIn / ImageOut filters for still-image pipelines (#21, #29); VideoIn moved off vidgear to cv2 / PyAV (#66, #67) for stability against odd-codec sources.

Breaking Changes

  • OTLP gRPC insecure flag now inferred from endpoint scheme (#90): the exporter factory and tracing builder use urllib.parse.urlparse on the configured endpoint — http:// infers plaintext, https:// infers TLS, and bare host:port infers TLS (secure default, matches the OTel SDK). Deployments configuring bare-host endpoints against plaintext collectors must prefix http:// to maintain prior behavior. Explicit insecure= in exporter_config / kwargs always wins.
  • Received Frame.image arrays are read-only (v0.1.28, re-surfaced for the SemVer-stable cut): topicmsgs2frames sets image.flags.writeable = False on both the zero-copy ZMQ and SHM transport paths — this prevents corruption of shared ring slots and re-enables the Frame.copy() share-buffer fast path and Frame.jpg encode caching, both of which gate on read-only status. Filters that mutate frame.image in place (e.g. cv2.rectangle(frame.image, ...), frame.image[y, x] = ...) must take a local copy first — image = frame.image.copy() — or construct a new frame via Frame(new_image, frame, frame.format). Symptom if not migrated: ValueError: assignment destination is read-only raised from the mutating call.

Stability commitments

Surfaces committed under 1.0 — breaking changes will require a 2.0 bump:

  • openfilter.filter_runtime.FilterConfigBase and its Managed / Resolve field shorthands, including the ResolveHint literal that parameterizes them
  • openfilter.filter_runtime.FilterOutputSchema and the shape catalog in openfilter.filter_runtime.shapes
  • The openfilter emit-schema CLI

The legacy dict-based FilterConfig continues to coexist with FilterConfigBase — unmigrated filters keep working unchanged.

Removed

  • OTLP_GRPC_ENDPOINT_SECURITY environment variable (#90): was previously a no-op — os.getenv(name, True) returned the literal True only when unset; any set value came back as a truthy string, so insecure=True regardless of operator intent.

Fixed

  • Silent $id inheritance on FilterOutputSchema subclasses (FILTER-452, #88): subclasses no longer silently inherit a parent's $id. __init_subclass__ refuses ambiguous construction at class-definition time.
  • test_topo_balance_step shutdown race on Python 3.10 (FILTER-461, #94): the TestFilterOld topology tests poll for runner termination instead of asserting on a single step() call after the shutdown sentinel. Test-only change; no runtime impact.

Infrastructure

  • gh release create uses secrets.GH_BOT_USER_PAT (FILTER-462, #97) so the release-tag push triggers cascade-on-tag.yaml automatically. Workflow permissions: tightened to contents: read.
  • Tag-triggered bump-PR cascade (DT-145, #85): cascade-on-tag.yaml workflow + scripts/cascade/* replace the previous cloudbuild-cascade.yaml mechanism. Fires on release-semver tag push, discovers eligible filter-* consumers, opens mechanical bump PRs through gh-actions-public/open-mechanical-pr.
  • Cascade widens consumer pyproject upper bounds when target excludes them (#93): 1.0+ targets widen to next-major (<2.0.0).

Release v0.2.1

Choose a tag to compare

@github-actions github-actions released this 16 May 20:22
1099680

This release rolls up the v0.2.0 declarative-configuration work (FILTER-441 / FILTER-442 / FILTER-444 / FILTER-452) and the v0.2.1 OTLP TLS inference change (#90) under a single tag. v0.2.0 was never tagged — its contents ship here under v0.2.1. Also folded in: the DT-145 cascade infrastructure (#85 / #93) and the FILTER-461 test-fragility fix (#94), all merged after the original v0.2.0 changelog window.

Added

  • Typed FilterConfigBase with emit_schema() (FILTER-441): New openfilter.filter_runtime.FilterConfigBase lets filters declare their config surface as a pydantic model and emit it as JSON Schema (draft 2020-12) for build-time consumption. Includes Managed / Resolve helpers for marking fields as orchestrator-controlled or platform-resolved, plus MANAGED_KEY / RESOLVE_KEY / PREFLIGHT_KEY extensions stamped onto the emitted schema. Fully opt-in — existing dict-based FilterConfig continues to work unchanged. (#86)
  • openfilter emit-schema CLI (FILTER-442): New CLI subcommand writes a filter's JSON Schema to stdout or -o <path>. Auto-detects the canonical class in a module or accepts an explicit module:Class qualifier. Default --kind config emits a FilterConfigBase schema; --kind output emits a FilterOutputSchema (FILTER-444). --include-managed surfaces orchestrator-controlled fields for platform inspection; default is operator-facing surface only. (#87)
  • FilterOutputSchema + frame.data shape catalog (FILTER-444): New openfilter.filter_runtime.FilterOutputSchema lets filters declare what they place on frame.data as a build-time JSON Schema. The catalog at openfilter.filter_runtime.shapes ships canonical shapes — BoundingBox, Polygon, Mask, Keypoint, Detection, DetectionSet, Track, TrackSet, Pose, PoseSet, KeypointSet, OCRSpan, OCRSpanSet, ClassificationResult — with stable $ids under https://schemas.plainsight.ai/shapes/<kebab>/v1. Filters reference catalog shapes via $ref instead of negotiating dialects out-of-band. Coordinate conventions (pixel-space for bbox/polygon/mask; normalized [0, 1] for keypoints) are documented per shape with the production filter that motivated each choice. Catalog shapes carry runtime-only invariants enforced by pydantic validators: BoundingBox xyxy ordering (zero-area allowed, inverted rejected), ClassificationResult parallel-array length equality, and Pose 17-keypoint arity when skeleton="coco-17". The whole FILTER-444 surface is re-exported from openfilter.filter_runtime so from openfilter.filter_runtime import FilterOutputSchema, Detection works the same way the FILTER-441 import idiom does. (#88)

Changed

  • OTLP gRPC insecure flag now inferred from endpoint scheme: The exporter factory and tracing builder now use urllib.parse.urlparse on the configured endpoint — http:// infers plaintext, https:// infers TLS, and bare host:port infers TLS (secure default, matches the OTel SDK). Explicit insecure= in exporter_config / kwargs always wins. The default http://localhost:4317 endpoint still infers insecure=True, so local collectors keep working with no config change. The metrics factory's missing localhost fallback was also fixed in this change — an unset endpoint no longer falls through to the SDK's TLS default against a plaintext local collector. (#90)

Fixed

  • Silent $id inheritance on FilterOutputSchema subclasses (FILTER-452): A subclass of any $id-bearing FilterOutputSchema (catalog shape or filter-author output) that did not explicitly override __schema_id__ silently inherited the parent's $id on the wire, causing two distinct classes to claim the same JSON Schema identity for $ref resolution. __init_subclass__ now refuses to construct such subclasses at class-definition time; authors must override __schema_id__ with their own URI or set it to None as an explicit opt-out. (#88)
  • test_topo_balance_step shutdown race on Python 3.10 (FILTER-461): After PR #82's zero-copy IPC tightened pipeline timing, four TestFilterOld topology tests' shutdown assertions started failing on 3.10 — a single runner.step() immediately after qout.put(None) was racing the filters' drain-and-report path. New wait_for_runner_exit helper polls step() up to 5 s for the exit-codes list. Test-only change, no runtime impact. (#94)

Removed

  • OTLP_GRPC_ENDPOINT_SECURITY environment variable: removed from the metrics exporter factory. This variable was previously a full no-op — os.getenv(name, True) returned the literal True only when unset; any set value came back as a string, and every non-empty string is truthy, so insecure=True regardless of what an operator wrote. No deployment was getting TLS through this var. The actual behavior change for operators is narrow: those running with a bare host:port endpoint now get TLS, where they used to get plaintext. Set insecure=True in exporter_config (or use an http:// URL) to keep plaintext. (#90)

Infrastructure

  • Tag-triggered bump-PR cascade (DT-145): New cascade-on-tag.yaml workflow and scripts/cascade/{discover.sh,bump-and-pr.sh,bump-strategy.sh,check_constraint.py} replace the previous cloudbuild-cascade.yaml mechanism (scripts/build-filters.sh removed in the same change). Fires on release-semver tag push, discovers eligible filter-* consumers via the GitHub Contents API + PEP 621 specifier intersection, opens mechanical bump PRs through PlainsightAI/gh-actions-public/open-mechanical-pr. Auto-merge enabled by default on bot PRs; workflow_dispatch inputs (dry_run, single_filter, filter_subset, auto_merge_override) support staged rollouts. (#85)
  • Cascade widens consumer pyproject upper bounds when target excludes them: bump-strategy.sh now rewrites <X / <=X upper bounds in consumer pyproject pins when the target openfilter version would otherwise be excluded. Rule: 0.X targets widen to next-minor; 1.0+ targets widen to next-major. Without this, every consumer pinning >=0.1.30,<0.2.0 (the org-canonical pattern) would skip the cascade for any minor/major openfilter bump — 30 of 55 filter-* repos in the current sweep. Lower-bound exclusions (>=X) and !=X exclusions still skip per the prior behavior. (#93)

Release v0.1.30

Choose a tag to compare

@github-actions github-actions released this 22 Apr 20:24
cb92d0b

Added

  • Token authentication and configurable CORS for HTTP filters: Webvis and REST filters now support opt-in token-based auth (auth_token / FILTER_AUTH_TOKEN) and configurable CORS origins (cors_origins / FILTER_CORS_ORIGINS). Auth accepts ?token= query params (for <img> MJPEG embeds) or Authorization: Bearer headers. CORS preflight bypasses auth. Both are fully backwards compatible — unset means no auth and Access-Control-Allow-Origin: *.

Changed

  • Shared security scan workflow: Replaced standalone Grype security scan with the shared PlainsightAI/gh-actions-public reusable workflow.
  • GitHub Actions bumped to latest versions: actions/checkout v4→v6, actions/setup-python v5→v6, actions/upload-artifact v4→v7, actions/download-artifact v4.1.3→v8, docker/setup-buildx-action v3→v4, docker/login-action v3→v4, docker/build-push-action v5→v6, dorny/paths-filter v3→v4, mukunku/tag-exists-action v1.6.0→v1.7.0. Resolves Node.js <24 deprecation warnings.
  • Docker images now include SLSA provenance and SBOM attestations via docker/build-push-action v6 defaults.

Fixed

  • CVE-2026-25645: Bumped requests from ~=2.32.5 to ~=2.33.0.
  • GHSA-8rrh-rw8j-w5fx: Bumped wheel from ~=0.45.1 to ~=0.46.2.
  • GHSA-cxww-7g56-2vh6: Bumped actions/download-artifact from v4.1.3 to v8.
  • Docker image build race condition: Added a wait-for-pypi step to the release workflow that polls PyPI until the newly published package is indexed before starting Docker image builds. Previously, some images could fail with No matching distribution found if PyPI index propagation hadn't completed.

Release v0.1.29

Choose a tag to compare

@github-actions github-actions released this 21 Apr 17:02
72586c2

Added

  • Frame accumulation support for batched processing: Filters can now set batch_size > 1 to accumulate frames and process them in batches via process_batch(). Includes timeout-based flushing, proper locking, and backward compatibility with single-frame process().
  • Single batch watcher thread: Replaced per-timeout threading.Timer with a single long-lived daemon thread for batch flush monitoring, reducing thread churn in high-throughput filters.

Release v0.1.28

Choose a tag to compare

@github-actions github-actions released this 15 Apr 05:26
fdbe7e6

Changed

  • Framework-agnostic GPU detection via ctypes: Replaced torch.cuda and nvidia-smi subprocess calls with direct ctypes.CDLL probing of CUDA/NVML shared libraries. Filters no longer need PyTorch installed just for GPU detection.

Release v0.1.27

Choose a tag to compare

@github-actions github-actions released this 03 Apr 03:08
c291174

Fixed

  • Remove eager imports from filters/__init__.py: The package-level __init__.py eagerly imported VideoOut, ImageOut, and ImageIn, which pulled in optional dependencies like PyAV (av). This crashed containers that only needed a subset of filters (e.g., video-in containers that don't have av installed). Since no code uses package-level imports (all consumers import directly from submodules), the re-exports were removed entirely.

Release v0.1.26

Choose a tag to compare

@github-actions github-actions released this 02 Apr 05:46
03efece

Added

  • OPENFILTER_APPEND_LD_LIBRARY_PATH and OPENFILTER_APPEND_PATH env vars: Read at filter startup (before torch import) and appended to the existing LD_LIBRARY_PATH / PATH values. Allows Kubernetes controllers to inject GPU driver paths without overriding container-image-baked paths. On GKE, the NVIDIA device plugin mounts drivers but does not set LD_LIBRARY_PATH; the pipelines controller can now inject the paths automatically via these variables.