Ejemplo n.º 1
0
    def __init__(self, *args, **kwargs):

        super(OpDLClassification, self).__init__(*args, **kwargs)

        self.predict = OpPixelwiseClassifierPredict(parent=self)
        self.predict.name = "OpClassifierPredict"
        self.predict.Image.connect(self.InputImage)
        self.predict.Classifier.connect(self.Classifier)
        self.predict.LabelsCount.connect(self.NumClasses)
        self.PredictionProbabilities.connect(self.predict.PMaps)

        self.prediction_cache = OpBlockedArrayCache(parent=self)
        self.prediction_cache.name = "BlockedArrayCache"
        self.prediction_cache.inputs["Input"].connect(self.predict.PMaps)
        self.prediction_cache.BlockShape.connect(self.BlockShape)
        self.prediction_cache.inputs["fixAtCurrent"].connect(self.FreezePredictions)
        self.CachedPredictionProbabilities.connect(self.prediction_cache.Output)

        self.opPredictionSlicer = OpMultiArraySlicer2(parent=self)
        self.opPredictionSlicer.name = "opPredictionSlicer"
        self.opPredictionSlicer.Input.connect(self.prediction_cache.Output)
        self.opPredictionSlicer.AxisFlag.setValue("c")
        self.PredictionProbabilityChannels.connect(self.opPredictionSlicer.Slices)

        self.opSegmentor = OpMaxChannelIndicatorOperator(parent=self)  # IMPROVEME: this "max channel" works for binary classification, but not when we have more than 2 classes
        self.opSegmentor.Input.connect(self.prediction_cache.Output)
        self.Segmentation.connect(self.opSegmentor.Output)

        self.opSegmentationSlicer = OpMultiArraySlicer2(parent=self)
        self.opSegmentationSlicer.name = "opSegmentationSlicer"
        self.opSegmentationSlicer.Input.connect(self.opSegmentor.Output)
        self.opSegmentationSlicer.AxisFlag.setValue("c")
        self.SegmentationChannels.connect(self.opSegmentationSlicer.Slices)
Ejemplo n.º 2
0
    def __init__(self, *args, **kwargs):
        super(OpRegionFeatures, self).__init__(*args, **kwargs)

        # Distribute the raw data
        self.opRawTimeSlicer = OpMultiArraySlicer2(parent=self)
        self.opRawTimeSlicer.name = 'OpRegionFeatures.opRawTimeSlicer'
        self.opRawTimeSlicer.AxisFlag.setValue('t')
        self.opRawTimeSlicer.Input.connect(self.RawImage)
        assert self.opRawTimeSlicer.Slices.level == 1

        # Distribute the labels
        self.opLabelTimeSlicer = OpMultiArraySlicer2(parent=self)
        self.opLabelTimeSlicer.name = 'OpRegionFeatures.opLabelTimeSlicer'
        self.opLabelTimeSlicer.AxisFlag.setValue('t')
        self.opLabelTimeSlicer.Input.connect(self.LabelImage)
        assert self.opLabelTimeSlicer.Slices.level == 1

        self.opRegionFeatures3dBlocks = OperatorWrapper(OpRegionFeatures3d, operator_args=[], parent=self)
        assert self.opRegionFeatures3dBlocks.RawVolume.level == 1
        assert self.opRegionFeatures3dBlocks.LabelVolume.level == 1
        self.opRegionFeatures3dBlocks.RawVolume.connect(self.opRawTimeSlicer.Slices)
        self.opRegionFeatures3dBlocks.LabelVolume.connect(self.opLabelTimeSlicer.Slices)
        self.opRegionFeatures3dBlocks.Features.connect(self.Features)
        assert self.opRegionFeatures3dBlocks.Output.level == 1

        self.opTimeStacker = OpMultiArrayStacker(parent=self)
        self.opTimeStacker.name = 'OpRegionFeatures.opTimeStacker'
        self.opTimeStacker.AxisFlag.setValue('t')
        assert self.opTimeStacker.Images.level == 1
        self.opTimeStacker.Images.connect(self.opRegionFeatures3dBlocks.Output)

        # Connect our outputs
        self.Output.connect(self.opTimeStacker.Output)
Ejemplo n.º 3
0
    def __init__(self, *args, **kwargs):
        super(OpPredictionPipeline, self).__init__(*args, **kwargs)

        # Random forest prediction using CACHED features.
        self.predict = OpClassifierPredict(parent=self)
        self.predict.name = "OpClassifierPredict"
        self.predict.Classifier.connect(self.Classifier)
        self.predict.Image.connect(self.CachedFeatureImages)
        self.predict.PredictionMask.connect(self.PredictionMask)
        self.predict.LabelsCount.connect(self.NumClasses)
        self.PredictionProbabilities.connect(self.predict.PMaps)

        # Alternate headless output: uint8 instead of float.
        # Note that drange is automatically updated.
        self.opConvertToUint8 = OpPixelOperator(parent=self)
        self.opConvertToUint8.Input.connect(self.predict.PMaps)
        self.opConvertToUint8.Function.setValue(lambda a:
                                                (255 * a).astype(numpy.uint8))
        self.PredictionProbabilitiesUint8.connect(self.opConvertToUint8.Output)

        # Prediction cache for the GUI
        self.prediction_cache_gui = OpSlicedBlockedArrayCache(parent=self)
        self.prediction_cache_gui.name = "prediction_cache_gui"
        self.prediction_cache_gui.inputs["fixAtCurrent"].connect(
            self.FreezePredictions)
        self.prediction_cache_gui.inputs["Input"].connect(self.predict.PMaps)
        self.CachedPredictionProbabilities.connect(
            self.prediction_cache_gui.Output)

        # Also provide each prediction channel as a separate layer (for the GUI)
        self.opPredictionSlicer = OpMultiArraySlicer2(parent=self)
        self.opPredictionSlicer.name = "opPredictionSlicer"
        self.opPredictionSlicer.Input.connect(self.prediction_cache_gui.Output)
        self.opPredictionSlicer.AxisFlag.setValue('c')
        self.PredictionProbabilityChannels.connect(
            self.opPredictionSlicer.Slices)

        self.opSegmentor = OpMaxChannelIndicatorOperator(parent=self)
        self.opSegmentor.Input.connect(self.prediction_cache_gui.Output)

        self.opSegmentationSlicer = OpMultiArraySlicer2(parent=self)
        self.opSegmentationSlicer.name = "opSegmentationSlicer"
        self.opSegmentationSlicer.Input.connect(self.opSegmentor.Output)
        self.opSegmentationSlicer.AxisFlag.setValue('c')
        self.SegmentationChannels.connect(self.opSegmentationSlicer.Slices)

        # Create a layer for uncertainty estimate
        self.opUncertaintyEstimator = OpEnsembleMargin(parent=self)
        self.opUncertaintyEstimator.Input.connect(
            self.prediction_cache_gui.Output)

        # Cache the uncertainty so we get zeros for uncomputed points
        self.opUncertaintyCache = OpSlicedBlockedArrayCache(parent=self)
        self.opUncertaintyCache.name = "opUncertaintyCache"
        self.opUncertaintyCache.Input.connect(
            self.opUncertaintyEstimator.Output)
        self.opUncertaintyCache.fixAtCurrent.connect(self.FreezePredictions)
        self.UncertaintyEstimate.connect(self.opUncertaintyCache.Output)
Ejemplo n.º 4
0
    def __init__(self, *args, **kwargs):
        super(OpPredictionPipeline, self).__init__(*args, **kwargs)

        # Random forest prediction using CACHED features.
        self.predict = OpClassifierPredict(parent=self)
        self.predict.name = "OpClassifierPredict"
        self.predict.Classifier.connect(self.Classifier)
        self.predict.Image.connect(self.CachedFeatureImages)
        self.predict.PredictionMask.connect(self.PredictionMask)
        self.predict.LabelsCount.connect(self.NumClasses)
        self.PredictionProbabilities.connect(self.predict.PMaps)

        # Prepare operator for Autocontext
        self.opConvertPMapsToInputPixelType = OpPixelOperator(parent=self)
        self.opConvertPMapsToInputPixelType.Input.connect(self.predict.PMaps)
        self.PredictionProbabilitiesAutocontext.connect(
            self.opConvertPMapsToInputPixelType.Output)

        # Prediction cache for the GUI
        self.prediction_cache_gui = OpSlicedBlockedArrayCache(parent=self)
        self.prediction_cache_gui.name = "prediction_cache_gui"
        self.prediction_cache_gui.inputs["fixAtCurrent"].connect(
            self.FreezePredictions)
        self.prediction_cache_gui.inputs["Input"].connect(self.predict.PMaps)
        self.CachedPredictionProbabilities.connect(
            self.prediction_cache_gui.Output)

        # Also provide each prediction channel as a separate layer (for the GUI)
        self.opPredictionSlicer = OpMultiArraySlicer2(parent=self)
        self.opPredictionSlicer.name = "opPredictionSlicer"
        self.opPredictionSlicer.Input.connect(self.prediction_cache_gui.Output)
        self.opPredictionSlicer.AxisFlag.setValue("c")
        self.PredictionProbabilityChannels.connect(
            self.opPredictionSlicer.Slices)

        self.opSegmentor = OpMaxChannelIndicatorOperator(parent=self)
        self.opSegmentor.Input.connect(self.prediction_cache_gui.Output)

        self.opSegmentationSlicer = OpMultiArraySlicer2(parent=self)
        self.opSegmentationSlicer.name = "opSegmentationSlicer"
        self.opSegmentationSlicer.Input.connect(self.opSegmentor.Output)
        self.opSegmentationSlicer.AxisFlag.setValue("c")
        self.SegmentationChannels.connect(self.opSegmentationSlicer.Slices)

        # Create a layer for uncertainty estimate
        self.opUncertaintyEstimator = OpEnsembleMargin(parent=self)
        self.opUncertaintyEstimator.Input.connect(
            self.prediction_cache_gui.Output)

        # Cache the uncertainty so we get zeros for uncomputed points
        self.opUncertaintyCache = OpSlicedBlockedArrayCache(parent=self)
        self.opUncertaintyCache.name = "opUncertaintyCache"
        self.opUncertaintyCache.Input.connect(
            self.opUncertaintyEstimator.Output)
        self.opUncertaintyCache.fixAtCurrent.connect(self.FreezePredictions)
        self.UncertaintyEstimate.connect(self.opUncertaintyCache.Output)
Ejemplo n.º 5
0
    def _initPredictionLayers(self, predictionSlot):
        layers = []
        opLane = self.topLevelOperatorView

        # Use a slicer to provide a separate slot for each channel layer
        opSlicer = OpMultiArraySlicer2(parent=opLane.viewed_operator().parent)
        opSlicer.Input.connect(predictionSlot)
        opSlicer.AxisFlag.setValue('c')

        for channel, channelSlot in enumerate(opSlicer.Slices):
            if channelSlot.ready():
                drange = channelSlot.meta.drange or (0.0, 1.0)
                predictsrc = LazyflowSource(channelSlot)
                predictLayer = AlphaModulatedLayer(
                    predictsrc,
                    tintColor=QColor.fromRgba(self._colorTable16[channel + 1]),
                    # FIXME: This is weird.  Why are range and normalize both set to the same thing?
                    range=drange,
                    normalize=drange)
                predictLayer.opacity = 1.0
                predictLayer.visible = True
                predictLayer.name = "Probability Channel #{}".format(channel +
                                                                     1)
                layers.append(predictLayer)

        return layers
Ejemplo n.º 6
0
    def __init__(self, *args, **kwargs):
        super(OpPredictionPipeline, self).__init__(*args, **kwargs)

        self.cacheless_predict = OpTikTorchClassifierPredict(parent=self)
        self.cacheless_predict.name = "OpTiktorchClassifierPredict (Cacheless Path)"
        self.cacheless_predict.ModelSession.connect(self.Classifier)
        self.cacheless_predict.Image.connect(
            self.RawImage)  # <--- Not from cache
        self.cacheless_predict.LabelsCount.connect(self.NumClasses)

        self.predict = OpTikTorchClassifierPredict(parent=self)
        self.predict.name = "OpTiktorchClassifierPredict"
        self.predict.ModelSession.connect(self.Classifier)
        self.predict.Image.connect(self.RawImage)
        self.predict.LabelsCount.connect(self.NumClasses)
        self.predict.BlockShape.connect(self.BlockShape)
        self.splitReqests = OpBlockedArrayCache(parent=self)
        self.splitReqests.Input.connect(self.predict.PMaps)
        self.splitReqests.BlockShape.connect(self.predict.BlockShape)
        self.PredictionProbabilities.connect(self.splitReqests.Output)

        self.prediction_cache = OpBlockedArrayCache(parent=self)
        self.prediction_cache.name = "BlockedArrayCache"
        self.prediction_cache.fixAtCurrent.connect(self.FreezePredictions)
        self.prediction_cache.BlockShape.connect(self.BlockShape)
        self.prediction_cache.Input.connect(self.predict.PMaps)
        self.CachedPredictionProbabilities.connect(
            self.prediction_cache.Output)

        self.opPredictionSlicer = OpMultiArraySlicer2(parent=self)
        self.opPredictionSlicer.name = "opPredictionSlicer"
        self.opPredictionSlicer.Input.connect(self.prediction_cache.Output)
        self.opPredictionSlicer.AxisFlag.setValue("c")
        self.PredictionProbabilityChannels.connect(
            self.opPredictionSlicer.Slices)
Ejemplo n.º 7
0
    def testBasicWithObjectDtype(self):
        """
        Make sure the slicer works even if dtype is 'object'
        """
        class SpecialNumber(object):
            def __init__(self, x):
                self.n = x
        
        data = numpy.ndarray(shape=(2,3), dtype=object)
        data = data.view(vigra.VigraArray)
        data.axistags = vigra.defaultAxistags('tc')
        for i in range(2):
            for j in range(3):
                data[i,j] = SpecialNumber(i*j)

        graph = Graph()
        opSlicer = OpMultiArraySlicer2(graph=graph)
        opSlicer.AxisFlag.setValue('t')
        opSlicer.Input.setValue( data )
        assert len(opSlicer.Slices) == 2
        assert opSlicer.Input.meta.dtype == numpy.object_
        assert opSlicer.Slices[0].meta.dtype == numpy.object_
        
        for i, slot in enumerate( opSlicer.Slices ):
            a = slot[:].wait()
            assert a.shape == (1,3)
            for j in range(3):
                val = a[0,j]
                assert type(val) == SpecialNumber
                assert val.n == i*j
Ejemplo n.º 8
0
 def setup_method(self, method):
     graph = Graph()
     self.graph = graph
     # Data is tagged by channel
     data = numpy.indices((10,10,10,3))[3]
     data = data.view(vigra.VigraArray)
     data.axistags = vigra.defaultAxistags('xyzc')
     
     self.opProvider = OpArrayPiper(graph=graph)
     self.opProvider.Input.setValue(data)
     
     self.opSlicer = OpMultiArraySlicer2(graph=graph)
     self.opSlicer.AxisFlag.setValue('c')
     self.opSlicer.Input.connect(self.opProvider.Output)
Ejemplo n.º 9
0
    def _initPredictionLayers(self, predictionSlot):
        layers = []

        colors = []
        names = []

        opLane = self.topLevelOperatorView

        if opLane.PmapColors.ready():
            colors = opLane.PmapColors.value
        if opLane.LabelNames.ready():
            names = opLane.LabelNames.value

        # Use a slicer to provide a separate slot for each channel layer
        opSlicer = OpMultiArraySlicer2(parent=opLane.viewed_operator().parent)
        opSlicer.Input.connect(predictionSlot)
        opSlicer.AxisFlag.setValue("c")

        colors = [QColor(*c) for c in colors]
        for channel in range(len(colors), len(opSlicer.Slices)):
            colors.append(PredictionViewerGui.DefaultColors[channel])

        for channel in range(len(names), len(opSlicer.Slices)):
            names.append("Class {}".format(channel + 1))

        for channel, channelSlot in enumerate(opSlicer.Slices):
            if channelSlot.ready(
            ) and channel < len(colors) and channel < len(names):
                predictsrc = createDataSource(channelSlot)
                predictLayer = AlphaModulatedLayer(predictsrc,
                                                   tintColor=colors[channel],
                                                   range=(0.0, 1.0),
                                                   normalize=(0.0, 1.0))
                predictLayer.opacity = 0.25
                predictLayer.visible = True
                predictLayer.name = names[channel]
                layers.append(predictLayer)

        return layers

        return colors
Ejemplo n.º 10
0
    def __init__(self, *args, **kwargs):

        super(OpNNClassification, self).__init__(*args, **kwargs)

        self.predict = OpPixelwiseClassifierPredict(parent=self)
        self.predict.name = "OpClassifierPredict"
        self.predict.Image.connect(self.InputImage)
        self.predict.Classifier.connect(self.Classifier)
        self.predict.LabelsCount.connect(self.NumClasses)
        self.PredictionProbabilities.connect(self.predict.PMaps)

        self.prediction_cache = OpBlockedArrayCache(parent=self)
        self.prediction_cache.name = "BlockedArrayCache"
        self.prediction_cache.inputs["Input"].connect(self.predict.PMaps)
        self.prediction_cache.BlockShape.connect(self.BlockShape)
        self.prediction_cache.inputs["fixAtCurrent"].connect(self.FreezePredictions)
        self.CachedPredictionProbabilities.connect(self.prediction_cache.Output)

        self.opPredictionSlicer = OpMultiArraySlicer2(parent=self)
        self.opPredictionSlicer.name = "opPredictionSlicer"
        self.opPredictionSlicer.Input.connect(self.prediction_cache.Output)
        self.opPredictionSlicer.AxisFlag.setValue("c")
        self.PredictionProbabilityChannels.connect(self.opPredictionSlicer.Slices)
Ejemplo n.º 11
0
    def __init__(self, *args, **kwargs):
        """
        Set up the internal pipeline.
        Since each labeling operator can only handle a single time and channel,
        we split the volume along time and channel axes to produce N 3D volumes, where N=T*C.
        The volumes are combined again into a 5D volume on the output using stackers.

        See ascii schematic in comments above for an overview.
        """
        super(_OpLabelImage, self).__init__(*args, **kwargs)

        self.opTimeSlicer = OpMultiArraySlicer2(parent=self)
        self.opTimeSlicer.AxisFlag.setValue("t")
        self.opTimeSlicer.Input.connect(self.Input)
        assert self.opTimeSlicer.Slices.level == 1

        self.opChannelSlicer = OperatorWrapper(OpMultiArraySlicer2,
                                               parent=self)
        self.opChannelSlicer.AxisFlag.setValue("c")
        self.opChannelSlicer.Input.connect(self.opTimeSlicer.Slices)
        assert self.opChannelSlicer.Slices.level == 2

        class OpWrappedVigraLabelVolume(Operator):
            """
            This quick hack is necessary because there's not currently a way to wrap an OperatorWrapper.
            We need to double-wrap OpVigraLabelVolume, so we need this operator to provide the first level of wrapping.
            """

            Input = InputSlot(level=1)
            BackgroundValue = InputSlot(optional=True, level=1)

            Output = OutputSlot(level=1)

            def __init__(self, *args, **kwargs):
                super(OpWrappedVigraLabelVolume,
                      self).__init__(*args, **kwargs)
                self._innerOperator = OperatorWrapper(OpVigraLabelVolume,
                                                      parent=self)
                self._innerOperator.Input.connect(self.Input)
                self._innerOperator.BackgroundValue.connect(
                    self.BackgroundValue)
                self.Output.connect(self._innerOperator.Output)

            def execute(self, slot, subindex, roi, destination):
                assert False, "Shouldn't get here."

            def propagateDirty(self, slot, subindex, roi):
                pass  # Nothing to do...

        # Wrap OpVigraLabelVolume TWICE.
        self.opLabelers = OperatorWrapper(OpWrappedVigraLabelVolume,
                                          parent=self)
        assert self.opLabelers.Input.level == 2
        self.opLabelers.Input.connect(self.opChannelSlicer.Slices)

        # The background labels will be converted to a VigraArray with axistags 'tc' so they can
        # be distributed to the labeling operators via slicers in the same manner as the input data.
        # Here, we set up the slicers that will distribute the background labels to the appropriate labelers.
        self.opBgTimeSlicer = OpMultiArraySlicer2(parent=self)
        self.opBgTimeSlicer.AxisFlag.setValue("t")
        assert self.opBgTimeSlicer.Slices.level == 1

        self.opBgChannelSlicer = OperatorWrapper(OpMultiArraySlicer2,
                                                 parent=self)
        self.opBgChannelSlicer.AxisFlag.setValue("c")
        self.opBgChannelSlicer.Input.connect(self.opBgTimeSlicer.Slices)
        assert self.opBgChannelSlicer.Slices.level == 2

        assert self.opLabelers.BackgroundValue.level == 2
        self.opLabelers.BackgroundValue.connect(self.opBgChannelSlicer.Slices)

        self.opChannelStacker = OperatorWrapper(OpMultiArrayStacker,
                                                parent=self)
        self.opChannelStacker.AxisFlag.setValue("c")

        assert self.opLabelers.Output.level == 2
        assert self.opChannelStacker.Images.level == 2
        self.opChannelStacker.Images.connect(self.opLabelers.Output)

        self.opTimeStacker = OpMultiArrayStacker(parent=self)
        self.opTimeStacker.AxisFlag.setValue("t")

        assert self.opChannelStacker.Output.level == 1
        assert self.opTimeStacker.Images.level == 1
        self.opTimeStacker.Images.connect(self.opChannelStacker.Output)

        # Connect our outputs
        self.Output.connect(self.opTimeStacker.Output)
Ejemplo n.º 12
0
    def __init__(self, *args, **kwargs):
        super(OpVigraWatershedViewer, self).__init__(*args, **kwargs)
        self._seedThreshold = None

        # Overview Schematic
        # Example here uses input channels 0,2,5

        # InputChannelIndexes=[0,2,5] ----
        #                                 \
        # InputImage --> opChannelSlicer .Slices[0] ---\
        #                                .Slices[1] ----> opAverage -------------------------------------------------> opWatershed --> opWatershedCache --> opColorizer --> GUI
        #                                .Slices[2] ---/           \                                                  /
        #                                                           \     MinSeedSize                                /
        #                                                            \               \                              /
        #                              SeedThresholdValue ----------> opThreshold --> opSeedLabeler --> opSeedFilter --> opSeedCache --> opSeedColorizer --> GUI

        # Create operators
        self.opChannelSlicer = OpMultiArraySlicer2(parent=self)
        self.opAverage = OpMultiArrayMerger(parent=self)
        self.opWatershed = OpVigraWatershed(parent=self)
        self.opWatershedCache = OpSlicedBlockedArrayCache(parent=self)
        self.opColorizer = OpColorizeLabels(parent=self)

        self.opThreshold = OpPixelOperator(parent=self)
        self.opSeedLabeler = OpVigraLabelVolume(parent=self)
        self.opSeedFilter = OpFilterLabels(parent=self)
        self.opSeedCache = OpSlicedBlockedArrayCache(parent=self)
        self.opSeedColorizer = OpColorizeLabels(parent=self)

        # Select specific input channels
        self.opChannelSlicer.Input.connect(self.InputImage)
        self.opChannelSlicer.SliceIndexes.connect(self.InputChannelIndexes)
        self.opChannelSlicer.AxisFlag.setValue('c')

        # Average selected channels
        def average(arrays):
            if len(arrays) == 0:
                return 0
            else:
                return sum(arrays) / float(len(arrays))

        self.opAverage.MergingFunction.setValue(average)
        self.opAverage.Inputs.connect(self.opChannelSlicer.Slices)

        # Threshold for seeds
        self.opThreshold.Input.connect(self.opAverage.Output)

        # Label seeds
        self.opSeedLabeler.Input.connect(self.opThreshold.Output)

        # Filter seeds
        self.opSeedFilter.MinLabelSize.connect(self.MinSeedSize)
        self.opSeedFilter.Input.connect(self.opSeedLabeler.Output)

        # Cache seeds
        self.opSeedCache.fixAtCurrent.connect(self.FreezeCache)
        self.opSeedCache.Input.connect(self.opSeedFilter.Output)

        # Color seeds for RBG display
        self.opSeedColorizer.Input.connect(self.opSeedCache.Output)
        self.opSeedColorizer.OverrideColors.setValue({0: (0, 0, 0, 0)})

        # Compute watershed labels (possibly with seeds, see setupOutputs)
        self.opWatershed.InputImage.connect(self.opAverage.Output)
        self.opWatershed.PaddingWidth.connect(self.WatershedPadding)

        # Cache the watershed output
        self.opWatershedCache.fixAtCurrent.connect(self.FreezeCache)
        self.opWatershedCache.Input.connect(self.opWatershed.Output)

        # Colorize the watershed labels for RGB display
        self.opColorizer.Input.connect(self.opWatershedCache.Output)
        self.opColorizer.OverrideColors.connect(self.OverrideLabels)

        # Connnect external outputs the operators that provide them
        self.Seeds.connect(self.opSeedCache.Output)
        self.ColoredPixels.connect(self.opColorizer.Output)
        self.SelectedInputChannels.connect(self.opChannelSlicer.Slices)
        self.SummedInput.connect(self.opAverage.Output)
        self.ColoredSeeds.connect(self.opSeedColorizer.Output)