Retrospective — Building XQMprViewer After Two Years of Production Qt/VTK Work
Retrospective
Published Jun 1, 2026
What went well
Starting XQMprViewer from a clean slate, after two years inside a
production medical imaging codebase, meant every architectural decision was
informed by specific pain I’d already felt. The pImpl isolation on the
viewport library, the IView/controller split, the decision to never let
Qt or VTK headers leak past the adapter boundary — none of these were
theoretical. They were responses to concrete maintenance costs I’d paid
repeatedly in the production system, where tangled Qt/VTK dependencies
made every refactor a cascade of recompilation.
The result is a codebase where adding a new rendering mode means writing
a new IView implementation and a controller, without touching the
existing viewport library or the DICOM loading path. That modularity
was the explicit goal, and it held.
What was harder than expected
The dual-topology comparison — viewport mode versus multi-window mode —
sounded like a straightforward experiment: same data, two render paths,
compare FPS. The hard part was making the comparison fair. A single
vtkRenderWindow shared across viewports handles OpenGL context sharing
internally; separate render windows each create their own context, and on
some drivers that means re-uploading textures per window. The FPS numbers
weren’t comparable until I normalized for texture state, which meant
instrumenting VTK’s internal pipeline at a level I hadn’t planned on.
The second surprise was how much of the engineering time went into the sphere annotation synchronization across planes, not the rendering itself. Keeping a draggable point consistent across axial, coronal, and sagittal views requires coordinate transforms that assume the volume’s origin and spacing are exactly right — a DICOM metadata assumption that’s correct in theory and subtly wrong in practice when files come from different scanners with different padding conventions.
What I’d do differently
I would write the DICOM metadata validation layer first, before any
rendering code. In both XQMprViewer and the production system, the
majority of hard-to-reproduce bugs traced back to unexpected metadata —
missing ImagePositionPatient, inconsistent PixelSpacing, slices
out of order. A dedicated validation pass that rejects or flags bad
data upfront would have saved more time than any rendering optimization.
The second thing: I’d commit to a test harness earlier. The project has manual visual tests but no automated pipeline for verifying that a rendered frame matches expected output. For a medical imaging tool, pixel-level regression testing isn’t optional — it’s the kind of discipline I’d enforce on a production system but skipped on a personal project because “I’ll add it later.” Later never comes.