Import Export
app.ui.qt.dialogs.import_exportImport / Export layer data dialog.
Shown when the user clicks either "Import / Export" button on the right panel. The dialog is scope-aware: the detection button opens it scoped to layers A/B, while the tracking button opens it scoped to layers C/D.
See Also
ImportMode— controls how imported boxes are mergedVideoDataLayer— identifies the layer scope for import and export
Result
After the user clicks OK, call ImportExportDialog.get_config to receive
an ImportExportConfig dataclass. If the dialog was rejected or the user
cancelled the overwrite-warning confirmation, get_config() returns None.
⚠️ KNOWN QT QVARIANT COERCION ISSUE — READ BEFORE EDITING
VideoDataLayer and ImportMode are both str-based enums:
class VideoDataLayer(str, Enum): A = "a" …
class ImportMode(str, Enum): OVERWRITE = "overwrite" …
When any Python object that is a subclass of str is stored as userData in a
QComboBox via addItem(label, userData) and retrieved via currentData(),
PySide6's internal Qt QVariant machinery sees a plain string (because the object
IS-A str), discards the Python subclass identity, and returns a bare str.
Concretely
self._layer_combo.addItem("Layer A", VideoDataLayer.A) # enum IN self._layer_combo.currentData() # "a" OUT ← coerced!
This caused
cfg.layer = "a" # plain str, not VideoDataLayer.A cfg.layer.name # AttributeError: 'str' object has no attribute 'name'
Observed in the logs as
WARNING | DetectionExportService was provided with string literal 'a' ERROR | AttributeError: 'str' object has no attribute 'name'
Fix applied in THREE places (belt-and-suspenders): 1. PRIMARY: _build_config() calls ensure_layer_enum() and ensure_import_mode() immediately after currentData() so coerced strings are converted back to enums before leaving the dialog. 2. SECONDARY: All import/export services coerce incoming layer/mode values at their entry points as a safety net for programmatic callers. 3. TERTIARY: Application facade methods coerce at the boundary before dispatching to detection/tracking service implementations.
No signals/slots are involved in the coercion — it is a synchronous round-trip through Qt's QVariant heap: QDialogButtonBox.accepted → _on_accepted() → _build_config() → currentData()
DO NOT remove ensure_layer_enum()/ensure_import_mode() calls without understanding this. See: app/application/services/layer_coercion.py
Layer: Presentation. Context: Contains concrete Qt presentation components and supporting utilities.
ImportExportConfig
dataclass
Store the import/export choices collected by ImportExportDialog.get_config.
| ATTRIBUTE | DESCRIPTION |
|---|---|
is_import |
TYPE:
|
layer |
The target/source
TYPE:
|
fmt |
TYPE:
|
file_path |
Absolute path selected by the user.
TYPE:
|
import_mode |
The chosen
TYPE:
|
Layer: Presentation. Context: Supports user interaction and delegates application work through presentation contracts.
file_path: str
instance-attribute
fmt: str
instance-attribute
import_mode: ImportMode | None
instance-attribute
is_import: bool
instance-attribute
layer: VideoDataLayer
instance-attribute
__init__(is_import: bool, layer: VideoDataLayer, fmt: str, file_path: str, import_mode: ImportMode | None) -> None
ImportExportDialog
Bases: QDialog
Modal dialog for importing or exporting bounding-box layer data.
| PARAMETER | DESCRIPTION |
|---|---|
scope_layers
|
Layers to offer in the layer combo box.
TYPE:
|
scope_title
|
Human-readable scope label shown in the window title.
TYPE:
|
parent
|
Optional Qt parent widget.
TYPE:
|
Example
dlg = ImportExportDialog(
scope_layers=[VideoDataLayer.A, VideoDataLayer.B],
scope_title="Detection",
parent=self,
)
if dlg.exec() == QDialog.DialogCode.Accepted:
cfg = dlg.get_config()
if cfg: # None if the user cancelled the overwrite warning
...
Note
The dialog enforces the overwrite warning itself (before calling
accept()) so the handler does not need to show a second dialog.
Consider future improvement: persist the last-used directory per scope
(detection / tracking) using QSettings so the file browser opens
in a sensible location across sessions.
Layer: Presentation. Context: Supports user interaction and delegates application work through presentation contracts.
__init__(scope_layers: list[VideoDataLayer], scope_title: str = 'Layer', initial_import_directory: str = '', initial_export_directory: str = '', parent: QWidget | None = None) -> None
get_config() -> ImportExportConfig | None
Return the user's choices after the dialog was accepted.
| RETURNS | DESCRIPTION |
|---|---|
ImportExportConfig | None
|
|