001 Notes
ClusterLens UI Runtime: Virtualized PyQt, Workers, And Generated State
1. Why Naive Galleries Fail
for every image in folder:
load full image from disk
decode image
resize image
create QLabel or custom widget
add widget to grid
| Problem | Why it hurts |
|---|---|
| one widget per image | widget creation, layout, style, and event cost explode |
| eager image decoding | startup waits on disk and CPU work |
| full-size image loading | memory pressure spikes |
| resize during paint | scrolling stutters |
| main-thread file I/O | the app stops responding |
| unbounded thumbnail work | old scroll positions keep wasting CPU |
| no cache policy | memory grows until the OS intervenes |
50,000 widgets
50,000 image decodes
50,000 layout items
50,000 paint targets
slow startup
blank window
frozen scroll
high memory usage
fan noise
crashes on large folders
2. Virtualization
large image list
-> model stores rows and metadata
-> view asks for visible rows
-> delegate paints visible cells
-> cache supplies thumbnails
-> workers fill missing thumbnails
flowchart LR
A[All image records] --> B[Model]
B --> C[View viewport]
C --> D[Visible indexes]
D --> E[Delegate paint]
E --> F[Thumbnail cache]
F --> G{Thumbnail ready?}
G -- yes --> H[Paint thumbnail]
G -- no --> I[Paint placeholder]
I --> J[Enqueue worker job]
| Component | Responsibility |
|---|---|
| model | row count, metadata, roles, selection state, cluster membership |
| view | scrolling, viewport, selection mechanics, keyboard/mouse behavior |
| delegate | paint visible cells/cards |
| worker | decode, resize, hash, embed, cluster, search |
| cache | avoid recomputing thumbnails and vectors |
Do not create UI objects for every image.
Create data records for every image.
Only paint visible records.
3. PyQt Model/View Pattern
rowCount()
columnCount()
data(index, role)
flags(index)
headerData()
data(index, role):
open image from disk
decode JPEG
resize thumbnail
create QPixmap
return pixmap
data(index, role):
if role asks for thumbnail:
if thumbnail exists in cache:
return cached thumbnail
enqueue thumbnail job if not already queued
return placeholder
if role asks for text:
return precomputed metadata
if role asks for status:
return cheap in-memory state
worker decodes image
worker resizes to thumbnail
worker stores result in cache
worker emits thumbnail_ready(image_id)
model emits dataChanged(index, index, [thumbnail_role])
view repaints that cell
data() returns what is already available.
data() may request work.
data() must not do heavy work itself.
4. Delegate Painting
thumbnail pixmap
placeholder rectangle
filename
cluster badge
selection border
loading indicator
warning marker
read files
run image decoding
query a database repeatedly
compute embeddings
perform layout-heavy widget creation
block on locks
paint(painter, option, index):
record = model record for index
thumbnail = model data for thumbnail role
draw background
draw thumbnail or placeholder
draw metadata badges
draw selection/focus state
5. The QPixmap And QImage Boundary
QPixmap And QImage Boundaryworker thread:
load image
decode into QImage or PIL image
resize
emit image bytes or QImage result
UI thread:
convert QImage to QPixmap if needed
store QPixmap in UI cache
notify view
worker prepares image data
UI thread owns GUI resources
Do CPU/disk work away from the main thread.
Keep GUI object mutation on the GUI thread.
6. Lazy Thumbnail Flow
sequenceDiagram
participant V as Viewport
participant M as Model
participant Q as Queue
participant W as Worker
participant C as Cache
V->>M: visible indexes changed
M->>C: thumbnail exists?
C-->>M: miss
M->>Q: enqueue visible thumbnail job
M-->>V: placeholder
W->>Q: take job
W->>W: decode and resize
W->>C: store thumbnail data
W-->>M: thumbnail ready
M-->>V: dataChanged(index)
visible rows first
near-future prefetch rows second
old offscreen work last or cancelled
visible_start = first visible row
visible_end = last visible row
prefetch_start = visible_start - margin
prefetch_end = visible_end + margin
deduplicate jobs by image id and thumbnail size
drop stale offscreen jobs when the viewport jumps
limit concurrent workers
prioritize current viewport
avoid generating thumbnails larger than needed
7. Cache Layers
| Cache | Purpose |
|---|---|
| in-memory pixmap cache | instant repaint of visible/recent thumbnails |
| in-memory image cache | reusable decoded/resized image data |
| disk thumbnail cache | avoid regenerating thumbnails between sessions |
| embedding cache | avoid rerunning model inference |
| perceptual hash index | avoid recomputing duplicate fingerprints |
| vector/search index | avoid rebuilding search state |
image path or id
file mtime
file size
thumbnail size
decode options
image path or id
file mtime
file size
model name
model signature
preprocess signature
runtime options if output can differ
ordered image set fingerprint
embedding model
similarity mode
backend
backend parameters
PCA settings
outlier policy
8. Worker Jobs
folder discovery
metadata scan
perceptual hash sync
thumbnail generation
embedding inference
vector index building
clustering
semantic search
duplicate candidate search
cache cleanup
file operations
model warm-up
| Field | Why it matters |
|---|---|
| job id | identify stale completions |
| input snapshot | freeze folder/query/settings for the run |
| progress callback | show useful status |
| cancellation flag | stop wasting work |
| result object | apply state safely on UI thread |
| error payload | show actionable failure |
| cleanup hook | release resources on finish/cancel |
worker reads live UI state while running
UI gathers current settings
UI creates immutable request object
worker only reads request object
9. Stale Job Protection
1. User selects folder A.
2. Clustering job A starts.
3. User quickly selects folder B.
4. Clustering job B starts.
5. Job A finishes after job B.
6. UI accidentally displays folder A results.
current_job_id = 42
worker result:
job_id = 41
UI:
if result.job_id != current_job_id:
ignore result
thumbnails
search results
cluster results
selection detail panels
AI summaries
warm-up jobs
cache cleanup status
10. Progress And Cancellation
scanning files
syncing perceptual hashes
loading cached embeddings
computing missing embeddings
building vector matrix
running clustering backend
building review model
generating thumbnails
writing result cache
Scanning folders...
Embedding 1,240 / 8,000 images
for batch in batches:
raise_if_cancelled()
process batch
report progress
11. Main Thread Discipline
input handling
painting
layout
light state updates
signal handling
small model notifications
image decoding
directory walking
database scans
embedding inference
clustering
network calls
large JSON parsing
bulk file moves
cache deletion
paintEvent
resizeEvent
data()
scroll handlers
selection changed handlers
timer callbacks
search text callbacks
12. Generated State
thumbnail cache
embedding database
perceptual hash index
vector index files
cluster result cache
model downloads
face/object index files if used
runtime temp files
logs
settings file
what exists
why it exists
where it is stored
how much disk it uses
whether it is rebuildable
what deleting it will do
| Category | User action |
|---|---|
| thumbnails | clear and regenerate |
| embeddings | clear and recompute |
| hash index | rebuild duplicate index |
| vector index | rebuild search index |
| model downloads | remove model assets |
| logs | delete logs |
| temp files | clear runtime leftovers |
| settings | open/change from UI |
13. Settings And Runtime Visibility
current folder
active model
embedding cache enabled/disabled
selected clustering backend
CPU/GPU runtime mode
thumbnail size
worker count/profile
cache location
generated storage usage
current job status
Processing...
Computing CLIP embeddings on CPU: 812 / 4,200 images
Cache hits: 3,388
Backend: MiniBatchKMeans
14. Safe File Actions
open containing folder
copy path
show file properties
move candidates to review folder
export candidate list
mark as not duplicate
clear generated cache
rebuild index
delete source images
bulk move images
overwrite metadata
rename many files
auto-delete near duplicates
show selected files
show evidence
show destination or deletion mode
require explicit confirmation
prefer move-to-trash over permanent delete
log action result
allow user to inspect failures
delete generated cache
delete downloaded model
move image file
delete image file
15. CPU, GPU, CUDA, And Packaging
| Mode | Product meaning |
|---|---|
| CPU default | portable baseline, easiest support |
| ONNX/native acceleration | optional speed path where available |
| Torch CUDA build | faster embedding inference on compatible NVIDIA systems |
| custom CUDA kernels | only claim if implemented, shipped, and benchmarked |
embedding inference
large tensor batches
matrix operations
nearest-neighbor search if a GPU backend is present
package size
driver compatibility
startup warm-up
memory pressure
fallback behavior
bug reports
release matrix
Runtime: CPU
Runtime: CUDA
Runtime: ONNX CPU
Runtime: ONNX GPU provider
GPU warm-up failed. Continuing on CPU.
Reason: incompatible runtime or unavailable device.
16. Model Warm-Up
load weights
initialize kernels
allocate buffers
compile/prepare runtime paths
populate caches
start app quickly
show UI
warm selected model in background if allowed
show warm-up status
allow cancellation
skip warm-up if model is not available locally
17. Shutdown Discipline
stop accepting new jobs
request cancellation on active workers
wait briefly for workers
flush pending cache writes
close database connections
release model/runtime resources
write final logs
avoid orphan processes
close during clustering
close during embedding
close during thumbnail generation
close during cache cleanup
close during warm-up
Finishing safe shutdown: cancelling clustering worker...
18. Failure Reporting
Failed.
Could not decode 14 images.
The rest of the scan completed.
Open error report?
unreadable image
permission denied
missing file after scan
model unavailable
GPU runtime unavailable
cache database corrupted
disk full
cancelled by user
19. What Was Done And Why
20. What Was Used, Removed, Or Deferred
virtualized model/view thinking
delegate painting
cheap data() contract
lazy thumbnail generation
bounded worker queues
progress and cancellation
stale job protection
generated-storage visibility
cache cleanup controls
CPU-first runtime behavior
optional GPU acceleration wording
safe file-action policy
shutdown discipline
one widget per image
image decoding in paint/data paths
main-thread embedding or clustering
unbounded thumbnail queues
hidden generated storage
destructive duplicate cleanup
silent GPU fallback
claiming custom CUDA kernels without shipped evidence
custom GPU thumbnail pipeline
custom CUDA clustering kernels
complex distributed indexing
automatic destructive file cleanup
global cache frameworks before simple cleanup controls
Comments