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.