QIM SDK Python App Builder Skill
Purpose
Use this skill to generate, debug, validate, and package QIM SDK Python applications. This skill is for Python qimsdk app artifacts only; it is not for C++, C sample apps, or gst-launch-1.0 command generation.
Operating Model
SKILL.md is the router. Detailed API facts, plugin facts, construction patterns, artifact rules, and verifier checks live in the purpose-named references below. Load only the references needed for the user's task so generation stays fast, low-token, and accurate.
Minimal Workflow
- Classify the request: API lookup, basic app, multimedia app, AI app, custom preprocess app, custom postprocess app, ML-bin app, AppSrc/AppSink bridge, YAML mode, edit/fix, or validation.
- Load
references/generation-rules.mdfor any generation, edit, validation, or review task. - For any Python app generation request, load
references/example-retrieval.mdand runrank_examples.pyto ground the draft in known-good examples — this adds context alongside the rest of this workflow, it does not replace steps 4-8. Finding a retrieval match does not skip or shorten any subsequent step — all of steps 4-8 run unconditionally regardless of what retrieval returns or whether a file was leveraged. Skip this step for plugin-only lookups. - Load only the task-specific references from the routing table.
- Use
references/plugin-catalog.mdfor plugin/property/module/runtime facts; do not invent or duplicate plugin facts. - If the request names a known model display name or
.tflitefilename, loadreferences/model-catalog.mdbefore filling module, delegate, labels/settings expectations, or mandatory results. - If missing information changes topology, element selection, runtime selection, custom preprocess tensor contract, or custom postprocess output type, ask a targeted clarification before generating.
- For generated artifacts, load
references/artifact-contract.md, create every required file, and runreferences/verify-python-app.shbefore declaring the artifact complete.
Non-Negotiable Invariants
- Use only instructions and facts from this skill folder (
SKILL.md+references/*.md). - Do not invent plugin names, properties, qimsdk APIs, postprocess modules, model stages, callback signatures, or Qualcomm-specific behavior.
- Keep plugin facts in
references/plugin-catalog.md; other references may show usage but must not become competing catalogs. - Known model facts live in
references/model-catalog.md; use it when the prompt names a known model or.tflitefilename. Catalog model facts override generic heuristics; user-provided paths and values still win. - Match the requested scope exactly and prefer the simplest documented topology that works.
- Ask when ambiguity changes topology; use placeholders only for runtime values that do not change structure.
- Generate artifact folders with the
qimsdk-python-prefix. - Artifact requests produce
main.pyandREADME.md; YAML-mode requests also produce a YAML config file unless the user explicitly says the YAML already exists and should not be generated. - If generated
main.pychanges, update the artifactREADME.mdin the same pass. - README must include
Pipeline FlowwithText SummaryandMermaid Diagramsubsections, plusSteps to Run; flow content must match the actual code. - For any app that uses
pulsesrcorpulsesink, include the PulseAudio prerequisite in the response and in READMESteps to Run:wpctl status, thenwpctl set-default <node_no.>. Do not invent a default node number;<node_no.>is device-specific. - README
Steps to Runmust first state that all models, labels, media, and other referenced files are present on the device, then showscp main.py <user>@<device-ip>:~/, thenssh <user>@<device-ip>andpython3 ~/main.py. Do not addchmod +x— Python apps run viapython3 <path>, not direct execution, so the executable bit is irrelevant; this is the one place this skill diverges from the GStreamer and C++ builder skills, which both chmod/execute a compiled or script artifact. - Full generated apps must define
create_and_execute_pipeline(...), definemain() -> None, and end withif __name__ == "__main__": main(). - Generated examples should include useful comments for pipeline sections, placeholders, custom preprocess/postprocess TODOs, and non-obvious SDK requirements. Do not comment every obvious line.
Construction Style
The SDK supports two construction styles.
Explicit Style - Default
Use explicit construction for new generated apps unless the user asks otherwise:
- create
Element, wrapper, and filter objects first - configure properties with
.set(...) - add objects with
pipeline.add(element) - use named variables for important stages
- link by element names with
pipeline.link(...), or rely on insertion-order auto-linking only for simple linear chains
Implicit Style - Supported
Implicit construction uses chained factory calls such as .add("factory", "name", "prop", value, ...).
Use implicit style when:
- the user explicitly asks for implicit/fluent/chained style
- editing an existing implicit app and the user did not ask for a style conversion
- preserving a small existing code pattern is lower risk than rewriting it
Do not mix explicit and implicit construction styles in one generated app unless the user explicitly asks for mixed style.
Python App Guardrails
Keep these rules router-visible because missing them commonly creates broken apps:
- Import from public
qimsdkexports. Do not import fromqimsdk._*internals unless the user explicitly asks. - Put executable pipeline construction and
pipeline.execute()insidecreate_and_execute_pipeline(...); keepmain() -> Nonefor logging setup, argument parsing, and callingcreate_and_execute_pipeline(...). Do not leave executable pipeline setup at module top level. - Use exact logging exports:
ImsdkLogLevel,ImsdkGstLogMode,SetImsdkLogLevel,SetImsdkGstLogMode. - Call logging setters before constructing the pipeline when generating a full app.
- Default generated apps to
SetImsdkGstLogMode(ImsdkGstLogMode.ImsdkLog)andSetImsdkLogLevel(ImsdkLogLevel.Debug)unless user explicitly requests a different logging mode or verbosity. - Use
VideoFilter().resolution(width, height)and.framerate(fps); do not invent.width()or.height(). - Expand
$HOMEin Python before passing any path to an element property. Element properties do not do shell expansion — a raw"$HOME/..."string fails to open. Usef"{os.environ['HOME']}/..."for every path constant (input/model/labels/output); do not useos.path.expandvars("$HOME/...")— it silently leaves the string unexpanded ifHOMEis unset instead of raising, reproducing the same unresolved-path failure. Seereferences/generation-rules.md. - Add
VideoFilter,TextFilter,TensorFilter,ImageFilter,H264Filter, and other stream filter objects withpipeline.add_stream_filter(...), never withpipeline.add(...). - In generated apps, always use the named
pipeline.add_stream_filter("<name>", filter_obj)form. Do not use the one-argument form, because repeated filter types such as twoTextFilter()instances collide on default element names liketextfilter. - Every stream filter name used in
pipeline.link(...)must exactly match the name passed topipeline.add_stream_filter(...), for examplepipeline.add_stream_filter("vf", vf),pipeline.add_stream_filter("mlf1", mlf1), andpipeline.add_stream_filter("mlf2", mlf2). - For AppSrc/AppSink callbacks, use
set_buffer_producer(...),set_buffer_consumer(...),set_preroll_handler(...),set_eos_handler(...), andset_enough_handler(...); for raw element signals (e.g. AppSrcneed-data) useconnect_signal("need-data", ...)(preferred overget_raw().connect(...)) only when explicitly requested.connect_signal/disconnect_signalare for generic GObject element signals not covered by a typed wrapper — do not use them to replace the AppSrc/AppSink handler callbacks, and do not generate genericset_handler(...)on AppSrc or AppSink. - For H.264 decode, default
v4l2h264dec capture-io-mode=4 output-io-mode=4unless user overrides with documented values, and place aqueueimmediately after hardware decode before any filter, tee, AI, display, or mux stage. - Normalize decoded/camera video to
VideoFilter().format("NV12")before branching or AI preprocessing unless user requests another documented format. - For
qticamsrc/qtiqmmfsrc, defaultcamera=0unless user asks for another camera. - For
qticamsrc/qtiqmmfsrccamera inputs, if the user does not provide resolution or framerate, constrain the camera stream withVideoFilter().format("NV12").resolution(1920, 1080).framerate(30)before display, branching, or AI preprocessing, and call out1920x1080 @ 30fpsas an assumed camera default in the artifact README. - For USB/V4L2 camera inputs, use
v4l2src device=<DEVICE_NODE> -> VideoFilter().format("YUY2") -> qtivtransform -> VideoFilter().format("NV12")before display, branching, or AI preprocessing; do not apply ISP-camera resolution/framerate defaults to USB unless the user requests them. - For display, default
waylandsink fullscreen=True sync=Truefor EVERY source type, including live camera sources (qticamsrc,qtiqmmfsrc,v4l2src) — camera source type alone is not a reason to usesync=False. Usesync=Falseonly for three exceptions: (1) more than 8 independent concurrently active input streams AND a processing-heavy topology (shared/batched inference, or a large composer grid where HTP/batch preroll makes frames arrive well behind their PTS, causingsync=Trueto drop late frames and freeze/blacken the display) — a 4-stream AI wall, a simple multi-stream playback grid, or a batch group of 8 or fewer streams stayssync=True; (2) display shares a tee/composer source with a parallel encode/file/metadata sink that can stall the display clock (stream-count-independent, can apply even with 1 stream) — pair withenable-last-sample=Falsewhen it is also a multi-sink camera pipeline; (3) the user explicitly requests lower latency over A/V sync. Audio-classification display pipelines must OMIT thesyncproperty entirely. - For metadata overlay with discrete AI elements, keep a main video branch with
tee, route postprocess metadata throughTextFilter(), and merge video + metadata withqtimetamuxbeforeqtivoverlay. - For segmentation, rendered-mask, alpha-blend, side-by-side, or other video-output postprocess branches, use direct-to-composer topology: passthrough branch plus AI/render branch into
qtivcomposer. Do not force rendered video throughTextFilter()/qtimetamux. - The render
VideoFilterafterqtimlpostprocessMUST be.format("RGBA")(neverBGRA/RGB— src caps arevideo/x-raw,{RGBA,RGBx}; anything else fails to link). Resolution: when the branch feeds aqtivcomposerwhose sink pad setsdimensions(segmentation/depth/super-res/detection tiles), do NOT also pin.resolution()on this filter — device-verified, it fails caps fixation withFixated width in filter caps is not supported with current post-process type!regardless of format; let the composer size it. Only pin.resolution(w, h)when there is NO composer pad geometry sizing the branch (e.g. a small overlay panel composited without per-pad dimensions). Seereferences/plugin-catalog.md"Module Output Types". - Set
qtivcomposerpad geometry viacomposer.input(N).set("position", [x, y])/.set("dimensions", [w, h])using Python LISTS. Never use gst-array strings like"<x, y>", never element-levelsink_N::positionproperties, never scalarx/y/width/height. Seereferences/api-surface.md. - When a parallel/overlay branch shares a tee with a
qtivcomposer(always a trigger — composer holds buffers for stream sync → the in-placeqtimetamux/qtivoverlaydraws nothing), a sibling branch hasfilesinkorqtimlmetaparser(required under load — these are fast but can still hold the buffer), a leaf videoAppSinkshares a tee withqtimlvconverter(poisons the DMA pool → 0 inference, "Buffer does not have FD memory"), or N sibling AI branches each overlay their own result off one shared tee with no composer at all (only one buffer-mutating consumer per shared tee pad can draw in place — the rest render nothing), insertqtivtransform ! VideoFilter(NV12)on each affected branch to force a private buffer copy. Seereferences/source-sink-patterns.md"Buffer Writability Under Shared Tees". - For super-resolution, audio AI, AI wall, batched multi-stream inference, face recognition, AI metadata parsing, mixed-wall, or zero-copy requests, use the named topology sections in
references/ai-pipeline-patterns.md; do not reduce these to the generic single-stream or two-stage templates. - For RTSP serving, multimedia audio/AV, multi-stream grid/fan-out, transform, or camera multi-pad/snapshot requests, load the expanded sections in
references/multimedia-pipeline-patterns.mdandreferences/source-sink-patterns.md. - For fused ML-bin overlay (
MLVideo*Bin/qtimlvideo*bin), keep ML-bin stages directly in the media path:source/decode -> VideoFilter(NV12) -> MLVideo*Bin -> qtivoverlay -> sink, or direct cascades such asmlbin1 -> mlbin2 -> qtivoverlay -> sink. Do not wrap ML-bin output in externaltee -> TextFilter() -> qtimetamuxmetadata fan-in; useqtimetamux/TextFilter()only for discrete AI branches that emit metadata separately. - For
qtimetamuxwritability, do not assume camera buffers are inherently non-writable; addqtivtransformbeforeqtimetamuxwhen the same source/tee also feedsqtivcomposer(always required — it holds buffers for stream sync), when a sibling branch hasfilesinkorqtimlmetaparser(required under load — these are fast but can still hold the buffer), when a documented source/format path requires conversion/copy, or when runtime logs show the metadata attach warning. - For two-stage daisy-chain AI, use stage-1
qtimlvconverter mode="image-batch-non-cumulative"and stage-2mode="roi-batch-cumulative". - HRNet (and other top-down pose models) can run either directly on full frames or on a cropped ROI from a preceding detector — both are legitimate topologies with a real accuracy/complexity tradeoff, not a fixed requirement. Full-frame is simpler (one stage, no detector needed) and works well when the subject fills most of the frame; a detection→ROI cascade adds a stage but improves keypoint accuracy on smaller/distant subjects by giving HRNet a tighter crop to work from. Build whichever the request actually describes; don't silently add or remove the detection stage. See
references/generation-rules.md"Known Model Catalog". lite-3dmm(facemap 3DMM, stage 2 of face recognition) requires/etc/data/{blendShape,meanFace,shapeBasis}.binon the device (hard-coded infacemap_3dmm_settings.json); without them stage 2 failsFailed to open /etc/data/meanFace.bin. Note this device prerequisite in the README. Seereferences/model-catalog.md.- For two-stage discrete daisy-chain AI with overlay of both stage outputs, do not generate a single linear
postprocess -> TextFilter -> qtimetamux -> qtivoverlaychain. Use two tee/mux stages: split before stage 1, merge stage-1 metadata withqtimetamux, split the merged stream before stage 2, merge stage-2 metadata with a secondqtimetamux, then overlay. - For gesture-recognition chains (palm detection -> hand landmark -> gesture embedding -> gesture classification), do not use a generic N-stage mux cascade. Use the documented gesture topology: exactly two
qtimetamuxstages,qtimetatransform module="roi-palmd"between the first mux and the second split, stage-2 inference output split into anhlandmarkmetadata branch and atensor -> embedder inference -> classifier inference -> mobilenetclassification branch, then merge both into the second mux beforeqtivoverlay. - For
qtimlpostprocess, usemodule,labels, and optionalsettings; includesettingsonly when user asks for config/settings/threshold tuning or provides a settings path. - For confidence threshold tuning, encode
settingsas JSON with the canonicalconfidencekey, for example{"confidence": 50.0}. Do not useconfidence_thresholdor semicolon-delimited settings. - For live camera or RTSP object/face/palm detection postprocess (
yolov8,yolov5,yolo-nas,qfd,palmd), setbbox-stabilization=True; omit it for file-source pipelines unless user requests stabilization. - Person-foot requests default to
module="qpd"when model/labels/settings paths indicate person-foot intent, unless user overrides. - PPE equipment/object detection and
gear_guard_netpaths default tomodule="yolov8"unless user overrides. - YOLOX detection defaults to
module="yolov8"unless user overrides. - For TFLite external delegate targeting HTP/NPU, always set
external-delegate-path="libQnnTFLiteDelegate.so"andexternal-delegate-options="QNNExternalDelegate,backend_type=htp,log_level=(string)1;"unless the user provides exact delegate options. - For secondary ROI inference stages where the user asks for high-performance HTP/NPU daisy-chain behavior, or for high-concurrency parallel HTP/NPU workloads, use
QNNExternalDelegate,backend_type=htp,htp_performance_mode=(string)2,log_level=(string)1;. For multi-batch HTP/NPU walls, use the named topology section and round-robin batch groups withhtp_device_idwhen multiple HTP devices are present. - Never emit
QNNExecurorBackend:HTP,QNNExecutorBackend:HTP,QNNExternalDelegateBackend:HTP, or colon-separated QNN delegate option strings. - For ML-bin wrappers, use
inference-*andpostprocess-*property families; do not mix discrete-element property names with bin property names. - For custom Python preprocess, use
MLVConverter("name").set(engine="none").set_handler(callback)for discrete pipelines orMLVideo*Bin.set("preprocess-engine", "none").set_preprocess_handler(callback)for ML-bin pipelines. - Do not add manual
signal.signal(SIGINT, ...)handling unless the user asks for custom shutdown behavior.
Custom Preprocess Guardrails
- Use custom Python preprocess only when requested or when the user explicitly asks for placeholder preprocess logic.
- For discrete pipelines, use
MLVConverter("preprocessing").set(engine="none").set_handler(callback). - For ML-bin wrappers, use
.set_preprocess_handler(callback)onMLVideoTFLiteBin,MLVideoQNNBin,MLVideoSNPEBin, orMLVideoONNXBin. - Callback signature is
(blits, outmlframe) -> bool. - For ML-bin wrappers with custom preprocess, set
"preprocess-engine", "none"before.set_preprocess_handler(...). blitscontainsMLVideoBlitentries; useblit.info,blit.destination, andblit.planes()only as placeholders unless the request provides exact tensor conversion logic. Do not instruct generated callbacks to callblit.unmap()manually; qimsdk unmaps blits after callback return.- Generate an honest TODO placeholder when the user does not provide tensor layout, scale/letterbox policy, quantization, channel order, and normalization details. Do not invent image-to-tensor conversion math.
- Placeholder preprocess callbacks return
Falsebecause no valid tensor was written. Generated comments must state that after implementing tensor conversion and writingoutmlframe.get_tensor(...), the callback must returnTrue. README must state that placeholder custom-preprocess artifacts are not functionally runnable for inference until real tensor-write logic is implemented.
Custom Postprocess Guardrails
- Use custom Python postprocess only when requested or when the user explicitly asks for placeholder postprocess logic.
- Use
MLPostprocess("name").set_handler(callback)for discrete postprocess stages. - ML-bin wrappers expose
.set_postprocess_handler(callback, kind=None)after the bin is constructed and contains an internalqtimlpostprocess. - Callback signatures must be either
(mlframe, mlparams, results)or(mlpostprocess, mlframe, mlparams, results). - Annotate the output parameter with a public marker type such as
ObjectDetections,Poses,Segmentations,DepthMaps,Tensors,ImageClassifications, orAudioClassifications. - Generated custom postprocess apps must include the explicit
gi.require_version("GstQtiML", "1.0")import block fromreferences/ml-postprocess.mdand mention theGstQtiML-1.0.typelibruntime dependency in README. - Functional callbacks must populate marker outputs with concrete
GstQtiML.*objects (Detection,Pose,Keypoint,KeypointLink,Segmentation,DepthMap,Classification) or writeTensorsoutputs as documented inreferences/ml-postprocess.md. - If the user does not provide tensor decode math, generate an honest placeholder callback with TODO comments. Do not invent decoding, scaling, NMS, keypoint layout, label mapping, or metadata population logic.
- Placeholder postprocess callbacks must not use a bare
return. They must leave the typed output object valid and returnTrue, even if it is empty, so downstreamTextFilter/qtimetamuxcan receive a buffer and the pipeline can advance past PAUSED. UseFalseonly for real validation/error branches. Add a TODO comment beside the stub explaining that decode/population logic is intentionally omitted.
Reference Routing
| Task | Load these references |
|---|---|
| Any generation, edit, validation, or review | references/generation-rules.md |
| API/method/import/style lookup | references/api-surface.md, references/sdk-architecture.md, references/pipeline-construction.md |
| Plugin/property/module/runtime lookup | references/plugin-catalog.md |
| Known model filename/display name lookup | references/model-catalog.md |
| Source, sink, decode, encode, display, AppSrc/AppSink, queue/tee/filter utility rules | references/source-sink-patterns.md |
| Multimedia-only camera/file/encode/display/playback patterns | references/multimedia-pipeline-patterns.md |
| AI, ML-bin, daisy-chain, overlay, custom preprocess/postprocess | references/ai-pipeline-patterns.md, references/ml-postprocess.md, references/inference-runtimes.md |
| YAML/config-driven app | references/pipeline-construction.md, references/api-surface.md |
| Artifact folder/files, README, verification | references/artifact-contract.md |
| Grounding a Python app generation in known-good examples | references/example-retrieval.md |
Common Load Sets
- Basic Python artifact:
generation-rules.md,example-retrieval.md,api-surface.md,pipeline-construction.md,source-sink-patterns.md,plugin-catalog.md,artifact-contract.md. - Multimedia artifact:
generation-rules.md,example-retrieval.md,multimedia-pipeline-patterns.md,source-sink-patterns.md,plugin-catalog.md,artifact-contract.md. - AI artifact:
generation-rules.md,example-retrieval.md,ai-pipeline-patterns.md,ml-postprocess.md,inference-runtimes.md,plugin-catalog.md,artifact-contract.md. - Custom preprocess or postprocess artifact: AI load set plus
api-surface.mdfor callback forms and public wrapper/marker types. - Plugin/API question only:
plugin-catalog.mdorapi-surface.mdas appropriate.
Verification Script
references/verify-python-app.shverifies generated Python artifacts.
Run it through the workflow in references/artifact-contract.md; do not treat script execution alone as a full contextual review.
Reference Files
references/generation-rules.md- common generation, clarification, style, placeholder, and comment rulesreferences/api-surface.md- public qimsdk imports, methods, wrappers, filters, lifecycle APIsreferences/sdk-architecture.md- qimsdk source layout and generation implicationsreferences/pipeline-construction.md- explicit and implicit construction patternsreferences/plugin-catalog.md- canonical plugin/property/module/runtime catalogreferences/model-catalog.md- known model-to-module/delegate/settings/results lookup tablereferences/source-sink-patterns.md- source/decode/sink patterns plus queue, tee, filters, mux/demux, links, and path utility rulesreferences/multimedia-pipeline-patterns.md- multimedia-only templates and constraintsreferences/ai-pipeline-patterns.md- AI/ML-bin/daisy-chain/overlay templatesreferences/ml-postprocess.md- built-in and custom Python postprocess guidancereferences/inference-runtimes.md- inference runtime and delegate property rulesreferences/artifact-contract.md- artifact layout, README contract, and verification workflowreferences/example-retrieval.md- retrieval-grounding: how to runrank_examples.py, screen ranked candidates for a genuine fit, and leverage a real matching file as the starting point (falling back to building fresh from the rules only when none fit).