-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Code Review: CrossSegmentationExplorer 3D Slicer Extension
Last Updated: January 29, 2026 (Updated Analysis)
Executive Summary
This is a 3D Slicer extension for comparing multiple segmentations on the same volume. The codebase has been partially updated since the initial review. While the module demonstrates substantial functionality and good use of Slicer APIs in many areas, several critical and moderate issues remain that should be addressed before production integration.
Status Summary: Changes Since Initial Review
| Issue Category | Status | Details |
|---|---|---|
| Filename Mismatch | ❌ NOT FIXED | File still named SegmentationComparison.py, should be CrossSegmentationExplorer.py |
| Dependencies Declared | ✅ PARTIALLY FIXED | QuantitativeReporting added, but pydicom still not formally managed |
| Error Handling | Some try-catch blocks added, but still using print() instead of logging |
|
| Icon URLs | ✅ FIXED | Now correctly points to ImagingDataCommons repository |
| Unit Tests | ❌ NOT FIXED | Test method still empty and trivial |
| Logging | ❌ NOT FIXED | Still using print() statements instead of proper logging |
1. Critical Issues
1.1 Incorrect Module Class Name vs. File Name
Severity: CRITICAL
Status: ❌ NOT FIXED
Location: SegmentationComparison.py
Issue:
class SegmentationComparison(ScriptedLoadableModule):
def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
self.parent.title = "CrossSegmentationExplorer"The Python file is still named SegmentationComparison.py but should be CrossSegmentationExplorer.py to match Slicer conventions. The CMakeLists.txt references ${MODULE_NAME}.py which expands to CrossSegmentationExplorer.py, creating a mismatch with the actual filename.
Impact: This causes module loading issues and confusion during integration. Slicer's discovery mechanism may fail to load the module correctly.
Recommended Fix:
- Rename
SegmentationComparison.pytoCrossSegmentationExplorer.py - Update all class names accordingly if needed for consistency
- Verify the CMakeLists.txt correctly references the renamed file
1.2 Unimplemented Test Cases
Severity: CRITICAL
Location: SegmentationComparison.py lines 1844-1882
Issue:
def test_SegmentationComparison1(self):
"""Ideally you should have several levels of tests..."""
self.delayDisplay("Starting the test")
# Test the module logic
logic = SegmentationComparisonLogic()
self.delayDisplay("Test passed")The test class creates a logic instance but performs no actual validation. The test will always pass trivially and provides no safety net for future modifications.
Impact: No automated validation of module functionality. Changes to core logic could break the module without detection.
Recommended Fix:
- Implement proper unit tests for logic class methods
- Test parameter node initialization
- Test segmentation loading and assignment
- Test layout generation XML
- Add integration tests that verify DICOM operations
1.3 Missing Error Handling in Critical Paths
Severity: HIGH
Status:
Location: Multiple locations including lines 725-750, lines 1759-1780
Issue Update:
Some try-catch blocks have been added, which is positive progress:
# Line 1759-1763: Added error handling
try:
dataset = pydicom.dcmread(series_files[0], stop_before_pixels = True, specific_tags = ["ReferencedSeriesSequence"])
return dataset.ReferencedSeriesSequence[0].SeriesInstanceUID
except Exception as e:
print(e) # ⚠️ Still uses print() instead of logging
return NoneHowever, critical issues remain:
-
Still using
print()instead of proper logging (lines 1763, 1803) -
No null checks in critical operations:
def onLoadSegmentations(self): volumeNode = self.ui.volumeNodeComboBox.currentNode() instanceUIDsString = volumeNode.GetAttribute("DICOM.instanceUIDs") # No null check if instanceUIDsString: # Code continues without checking if database/paths are valid
-
Generic exception handling masks specific errors:
except Exception as e: # Catches everything, unclear what can fail print(e)
-
No user-facing error messages - users won't know why operations fail
Impact: Improved from initial state, but still inadequate. Users get cryptic failures without helpful feedback.
Recommended Fix:
- Replace all
print(e)with proper logging - Add specific exception types instead of bare
except Exception: - Use
slicer.util.errorDisplay()for user-facing errors - Add null checks for DICOM database and file paths
- Log operation progress for debugging
2. Major Issues
2.1 Hardcoded Layout Names and Magic Strings
Severity: MAJOR
Location: lines 1612-1630
Issue:
for sliceViewName in ["R", "G", "Y"]:
for modelName, segmentationNodes in segmentationMapping2D.items():
sliceWidget = layoutManager.sliceWidget(f"{sliceViewName} {modelName}")
# ...
viewNode = sliceWidget.mrmlSliceNode()Hard-coded assumptions about slice view names ("R", "G", "Y") and naming conventions are fragile. If Slicer's layout changes or if there are edge cases with special characters in model names, this breaks.
Impact: Potential crashes if layout assumptions are violated. Non-standard characters in model names could cause failures.
Recommended Fix:
- Validate that sliceWidget is not None before accessing it
- Use try-except or explicit None checks
- Consider using constants defined at module level for view names
- Add validation for model names (sanitize special characters)
2.2 Performance Issue with Disabled Code
Severity: MAJOR
Location: lines 727-728 in README and referenced code
Issue:
The README acknowledges a known performance problem and provides instructions to comment out code:
# referencedSegmentations = self.getAssociatedSegmentationFileNumber()
# self.ui.lableSegdicomRef.setText(str(referencedSegmentations) + " DICOM SEG series referencing this volume found")The README states: "Selecting a volume takes very long at the moment... If needed, you can temporarily disable this by commenting out..."
This is a serious design issue. Users shouldn't be required to edit source code to get acceptable performance.
Impact: Poor user experience on first run. Users may lose data or workflows are unusable without modifying code.
Recommended Fix:
- Cache DICOM query results
- Use background threads/async operations for DICOM lookups
- Implement incremental loading
- Consider moving expensive operations out of the UI thread
2.3 Inconsistent Parameter Node Synchronization
Severity: MAJOR
Location: Multiple locations, particularly lines 440-490
Issue:
def setupOptions(self):
# Gets from parameter node first
mapping_json = self._parameterNode.GetParameter("ModelKeywordMapping")
if not mapping_json:
settings = qt.QSettings()
mapping_json = settings.value("SegmentationComparison/ModelKeywordMapping", "") or "{}"The pattern of checking parameter node, then falling back to QSettings appears inconsistently. In some places, settings are saved to both, in others to only one. This creates ambiguity about the source of truth and can lead to data loss or unexpected behavior when scenes are saved/loaded.
Impact: Confusion about which settings persist across sessions. Potential data loss.
Recommended Fix:
- Establish a clear hierarchy: parameter node is always primary, sync to settings for persistence
- Document the persistence strategy clearly
- Use one consistent pattern throughout
2.4 Missing DICOM Dependency Declaration
Severity: MAJOR
Status: ✅ FIXED
Location: CMakeLists.txt line 13
Update: The dependency has been corrected!
set(EXTENSION_DEPENDS "QuantitativeReporting")Previously this was set to "NA", but has now been properly updated to declare the QuantitativeReporting extension as a dependency.
Remaining Issues:
- The
pydicompackage is still not formally declared as a requirement - No mechanism to check/install
pydicomat module load time - The README mentions it's required but there's no automatic installation
Recommended Fix:
- Create a
requirements.txtorsetup.pyfor Python dependencies - Add a check in the module
__init__to validatepydicomis available - Provide helpful error message if
pydicomis missing
2.5 Incomplete Imports and Missing Dependency Management
Severity: MAJOR
Location: lines 1-13
Issue:
import DICOMLib
import DICOMLib.DICOMUtils
import pydicomThese imports are used but not declared as requirements. pydicom in particular is an external package that must be available. There's no mechanism to ensure it's installed.
Impact: Module may fail to load on fresh Slicer installations without explicit pydicom installation.
Recommended Fix:
- Create a
requirements.txtor declare dependencies in CMakeLists.txt - Add import error handling with helpful messages
- Check Slicer documentation for proper dependency declaration patterns
3. Moderate Issues
3.1 Global Scene Event Handler with No Cleanup
Severity: MODERATE
Location: lines 14-31
Issue:
def _restoreCustomLayout(caller, event):
# ... implementation
slicer.mrmlScene.AddObserver(slicer.mrmlScene.EndImportEvent, _restoreCustomLayout)This observer is registered at module import time, not at widget initialization. If the module is reloaded, this observer is registered again, creating duplicate observers.
Impact:
- Multiple redundant callbacks on scene events
- Potential memory leaks on module reload
- Difficult debugging
Recommended Fix:
class SegmentationComparison(ScriptedLoadableModule):
def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
# Register observer in __init__, not at module level
# Later: unregister in cleanup3.2 Race Condition in Scene Operations
Severity: MODERATE
Location: lines 191-200
Issue:
self.addObserver(slicer.mrmlScene, slicer.mrmlScene.NodeAddedEvent, self._buildSegmentationVolumeMap)
self.addObserver(slicer.mrmlScene, slicer.mrmlScene.NodeRemovedEvent, self._buildSegmentationVolumeMap)These observers can trigger rapidly during batch operations (e.g., loading a study with multiple segmentations). This calls _buildSegmentationVolumeMap many times redundantly.
def _buildSegmentationVolumeMap(self, caller=None, event=None):
"""Creates a global dict..."""
self._segmentationVolumeMap.clear()
for segmentation in slicer.util.getNodesByClass('vtkMRMLSegmentationNode'):
# ... expensive operationImpact: Performance degradation when loading multiple nodes simultaneously.
Recommended Fix:
- Implement debouncing (coalesce multiple rapid calls)
- Use a dirty flag instead of recalculating on every change
3.3 Type Annotation Missing
Severity: MODERATE
Location: Throughout the file
Issue:
Most functions lack proper type hints:
def getCheckedModels(self): # Should indicate return type
def updateLayout(self): # Parameters and return types unclearImpact: Reduces code readability and IDE support, harder to catch type-related bugs.
Recommended Fix:
- Add type hints to function signatures
- Use Python 3.9+
from __future__ import annotationsif needed
3.4 Weak Input Validation for User Input
Severity: MODERATE
Location: lines 465-495 (keyword table operations)
Issue:
def onModelTableItemChanged(self):
mapping = self.logic.readMappingTable(self.dialogUi.modelNametableWidget)
# No validation of model names or keywords
# Empty strings, special characters not validatedImpact: User can create invalid configurations that fail silently.
Recommended Fix:
- Validate model names (non-empty, no leading/trailing spaces)
- Validate keywords format
- Provide user feedback for invalid entries
3.5 Resource File Icons May Not Load
Severity: MODERATE
Location: lines 81-87
Issue:
base_path = os.path.dirname(os.path.abspath(__file__))
self._icons = {
'header_visible': qt.QIcon(os.path.join(base_path, "Resources", "Icons", "SlicerVisibleInvisible.png")),
# ...
}No error handling if icon files don't exist. These paths assume a specific directory structure that may not be guaranteed during development or in some deployment scenarios.
Impact: Silent icon loading failures, UI appears broken with missing icons.
Recommended Fix:
icon_path = os.path.join(base_path, "Resources", "Icons", "SlicerVisibleInvisible.png")
if os.path.exists(icon_path):
self._icons['header_visible'] = qt.QIcon(icon_path)
else:
logging.warning(f"Icon not found: {icon_path}")4. Minor Issues
4.1 Inconsistent Logging
Severity: MINOR
Location: Multiple locations including line 1761
Issue:
except Exception as e:
print(e) # Uses print instead of logging moduleThe module uses print() for error output instead of Python's logging module. This makes it difficult to capture logs or adjust verbosity.
Recommended Fix:
import logging
logger = logging.getLogger(__name__)
except Exception as e:
logger.error(f"Failed to read DICOM file: {e}", exc_info=True)4.2 Typos and Grammar Issues
Severity: MINOR
Location: Multiple locations
Examples:
- Line 87:
'invisible' : qt.QIcon(...)- inconsistent spacing around colons - Line 520:
setToolTip("Group multiple..." )- Extra space before parenthesis - Function
showsegmentationModelDialog[line 497] - Should beshowSegmentationModelDialog(camelCase)
Impact: Minor code style inconsistencies.
4.3 Unclear Variable Names
Severity: MINOR
Location: Various locations
Issue:
# Line 1210
counts, info = self.logic.prepareSegmentationData(mapping)
structureNames = list(counts.keys())
# What does 'counts' represent? Not immediately clearBetter name: segmentationCounts or structureFrequencies
Recommended Fix:
Use more descriptive variable names that clearly indicate what data is stored.
4.4 Commented Out Debug Code
Severity: MINOR
Location: lines 1259-1260
Issue:
#palette = table.palette
#base_color = palette.color(qt.QPalette.Base)
#alt_base_color = palette.color(qt.QPalette.AlternateBase)Commented code should be removed or documented with TODO comments explaining why it's kept.
4.5 Extension Icon URL References
Severity: MINOR
Status: ✅ FIXED
Location: CMakeLists.txt lines 10-11
Update: The URLs have been corrected!
set(EXTENSION_ICONURL "https://raw.githubusercontent.com/ImagingDataCommons/CrossSegmentationExplorer/main/CrossSegmentationExplorer/Resources/Icons/SegmentationComparison.png")Previously pointed to the old "SlicerSegmentationVerification" repository, now correctly references the ImagingDataCommons/CrossSegmentationExplorer repository.
Status: ✅ RESOLVED
5. API Usage and Compliance
5.1 Proper Use of Parameter Nodes ✓
The module correctly uses parameter nodes for persisting state across scene saves and reloads. This is good practice.
5.2 Proper Use of Observers ✓
The module correctly registers and manages observers for MRML events, though with noted issues about module reloading.
5.3 Proper Layout Management ✓
XML generation for custom layouts follows Slicer's API correctly.
5.4 DICOM Operations Need Improvement
While the module uses DICOM APIs correctly in most cases, the error handling and dependency management needs improvement (see section 2.1-2.4).
6. Summary of Recommended Actions
BLOCKING Issues (Must Fix Before Integration):
- ❌ Rename Python file -
SegmentationComparison.py→CrossSegmentationExplorer.py - ❌ Implement real unit tests - Current test is empty placeholder
⚠️ Replace print() with logging - Still 2+ debug print statements (lines 1763, 1803)
High Priority (Should Fix):
⚠️ Complete error handling - Add null checks and user-facing error dialogs- ❌ Fix performance issue - getAssociatedSegmentationFileNumber() is slow (acknowledged in README)
⚠️ Manage pydicom dependency - Add import validation and helpful error messages⚠️ Fix module reload issues - Global observer registration
Medium Priority (Nice to Have):
- Add type hints throughout
- Improve variable naming clarity
- Add input validation for user-editable tables
- Implement debouncing for rapid node changes
- Use const definitions for hardcoded values ("R", "G", "Y")
7. Testing Recommendations
- Create integration tests that verify DICOM loading workflows
- Add unit tests for layout XML generation
- Test parameter node persistence
- Add tests for edge cases (missing files, invalid DICOM, etc.)
- Test module reload behavior
Conclusion
The CrossSegmentationExplorer extension demonstrates good understanding of 3D Slicer's architecture and APIs. Recent updates have fixed some issues (dependency declarations, URL references), but critical problems remain that prevent production integration.
Current Status:
- ✅ 2 out of 7 blocking issues fixed
⚠️ Some progress on error handling, but incomplete- ❌ Core architectural issue (filename mismatch) still unresolved
- ❌ Test coverage still non-existent
Before Release:
The extension is not ready for production deployment without addressing the blocking issues:
- File must be renamed to match Slicer conventions
- Meaningful tests must be written (currently a placeholder)
- Logging must replace print() statements
- Complete error handling in all critical paths
After These Fixes:
The extension would benefit from additional improvements for robustness, performance, and maintainability as outlined in Sections 2-4.
The codebase shows solid engineering practices in many areas (proper parameter node usage, layout management, observer patterns). With the remaining fixes, this could be a well-integrated Slicer extension.