Skip to content

TODO: Copilot Review #12

@LenaGiebeler

Description

@LenaGiebeler

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 ⚠️ PARTIALLY FIXED 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.py to CrossSegmentationExplorer.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: ⚠️ PARTIALLY ADDRESSED

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 None

However, critical issues remain:

  1. Still using print() instead of proper logging (lines 1763, 1803)

  2. 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
  3. Generic exception handling masks specific errors:

    except Exception as e:  # Catches everything, unclear what can fail
        print(e)
  4. 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 pydicom package is still not formally declared as a requirement
  • No mechanism to check/install pydicom at module load time
  • The README mentions it's required but there's no automatic installation

Recommended Fix:

  • Create a requirements.txt or setup.py for Python dependencies
  • Add a check in the module __init__ to validate pydicom is available
  • Provide helpful error message if pydicom is missing

2.5 Incomplete Imports and Missing Dependency Management

Severity: MAJOR

Location: lines 1-13

Issue:

import DICOMLib
import DICOMLib.DICOMUtils
import pydicom

These 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.txt or 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 cleanup

3.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 operation

Impact: 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 unclear

Impact: 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 annotations if 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 validated

Impact: 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 module

The 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 be showSegmentationModelDialog (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 clear

Better 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):

  1. Rename Python file - SegmentationComparison.pyCrossSegmentationExplorer.py
  2. Implement real unit tests - Current test is empty placeholder
  3. ⚠️ Replace print() with logging - Still 2+ debug print statements (lines 1763, 1803)

High Priority (Should Fix):

  1. ⚠️ Complete error handling - Add null checks and user-facing error dialogs
  2. Fix performance issue - getAssociatedSegmentationFileNumber() is slow (acknowledged in README)
  3. ⚠️ Manage pydicom dependency - Add import validation and helpful error messages
  4. ⚠️ Fix module reload issues - Global observer registration

Medium Priority (Nice to Have):

  1. Add type hints throughout
  2. Improve variable naming clarity
  3. Add input validation for user-editable tables
  4. Implement debouncing for rapid node changes
  5. 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:

  1. File must be renamed to match Slicer conventions
  2. Meaningful tests must be written (currently a placeholder)
  3. Logging must replace print() statements
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions