fix(download): 32-bit Signal truncates byte counts for model files >2 GiB (corrupt progress display)
This was generated by AI during a kaizen pass.
Summary
The model-download progress dialog (labelme/_widgets/download.py) marshals byte counts through a Signal(int, ...), which Qt binds to a native 32-bit signed int. For a single downloaded file larger than 2 GiB (2**31 - 1 bytes) the byte count silently wraps around, so the progress dialog shows garbage byte counts and a wrong/negative progress bar. The download itself still succeeds (the actual transfer is done by gdown inside osam, independent of the Qt signal); only the progress display is corrupted.
This is reachable in practice: large single-file models such as SAM ViT-H (~2.5 GB) are selectable, and bytes_total is reported per file by gdown, not summed, so a single file can exceed the limit.
Reproduction
download.py:22 declares:
progress = Signal(int, int, str, int, int) # bytes_so_far / bytes_total are the last twoConfirmed against the shipped binding (PySide6 6.11.1):
signal: [('int', -1794967296), ('qint64', 2500000000)]
setRange-max -> RAISED OverflowError
setValue -> RAISED OverflowErrorSignal(int).emit(2_500_000_000)delivers-1794967296(wrapped mod 2**32, reinterpreted signed).QProgressDialog.setRange(0, 2_500_000_000)andsetValue(2_500_000_000)both raiseOverflowError(also 32-bit).
Those wrapped values then flow into _on_progress (download.py:100-120): dialog.setRange(0, bytes_total) / dialog.setValue(bytes_so_far) get a negative/garbage range, and _format_bytes(bytes_so_far) / _format_bytes(bytes_total) renders nonsense like 672 MB / -1712 MB.
Why this needs a human decision (not a blind kaizen PR)
The fix is necessarily two-part, and the second part is a display-behavior decision:
Signal transport — widen the two byte-count fields to 64-bit, e.g.
Signal(int, int, str, "qint64", "qint64"), so_format_bytesshows accurate totals. This part is cheaply unit-testable (emit large values through_DownloadThread.run()with a fakepull(), assert the received values are not truncated).QProgressDialogis itself 32-bit. Once the signal carries the true2.5e9,setRange(0, bytes_total)/setValue(bytes_so_far)would then raiseOverflowError(verified above) — i.e. fixing only the signal converts today's cosmetic bug into a real exception in the slot. So the sink must scale the values into 32-bit range before handing them to the dialog. That scaling scheme has more than one reasonable answer:- fixed resolution (e.g.
setRange(0, 1000),setValue(round(1000 * so_far / total))), - unit scaling (
bytes // 1024, KB granularity), or - percentage (
0..100).
- fixed resolution (e.g.
Additionally, the end-to-end behavior can only be truly verified by triggering a real multi-GB download, which is impractical in CI — so an unattended kaizen PR cannot /verify it.
Affected files
labelme/_widgets/download.py—progressSignal declaration (line 22) and the_on_progresssink (lines 100-120).
Suggested resolution
Pick a scaling scheme for the 32-bit QProgressDialog (fixed resolution is the least surprising and keeps the byte-count label driven by the raw 64-bit values), widen the Signal to qint64, and add a unit test asserting the transported byte counts survive round-trip above 2 GiB.
Source: wkentaro/labelme