Esempio n. 1
0
    def _create_rgba_layer_from_slot(cls, slot, numChannels, lastChannelIsAlpha):
        bindex = aindex = None
        rindex, gindex = 0,1
        if numChannels > 3 or (numChannels == 3 and not lastChannelIsAlpha):
            bindex = 2
        if lastChannelIsAlpha:
            aindex = numChannels-1

        if numChannels>=2:
            gindex = 1
        if numChannels>=3:
            bindex = 2
        if numChannels>=4:
            aindex = numChannels-1

        redSource = None
        if rindex is not None:
            redProvider = OpSingleChannelSelector(parent=slot.getRealOperator().parent)
            redProvider.Input.connect(slot)
            redProvider.Index.setValue( rindex )
            redSource = createDataSource( redProvider.Output )
            redSource.additional_owned_ops.append( redProvider )
        
        greenSource = None
        if gindex is not None:
            greenProvider = OpSingleChannelSelector(parent=slot.getRealOperator().parent)
            greenProvider.Input.connect(slot)
            greenProvider.Index.setValue( gindex )
            greenSource = createDataSource( greenProvider.Output )
            greenSource.additional_owned_ops.append( greenProvider )
        
        blueSource = None
        if bindex is not None:
            blueProvider = OpSingleChannelSelector(parent=slot.getRealOperator().parent)
            blueProvider.Input.connect(slot)
            blueProvider.Index.setValue( bindex )
            blueSource = createDataSource( blueProvider.Output )
            blueSource.additional_owned_ops.append( blueProvider )

        alphaSource = None
        if aindex is not None:
            alphaProvider = OpSingleChannelSelector(parent=slot.getRealOperator().parent)
            alphaProvider.Input.connect(slot)
            alphaProvider.Index.setValue( aindex )
            alphaSource = createDataSource( alphaProvider.Output )
            alphaSource.additional_owned_ops.append( alphaProvider )
        
        layer = RGBALayer( red=redSource, green=greenSource, blue=blueSource, alpha=alphaSource)
        normalize = cls._should_normalize_display(slot)
        for i in range(4):
            if [redSource,greenSource,blueSource,alphaSource][i]:
                layer.set_range(i, slot.meta.drange)
                layer.set_normalize(i, normalize)

        return layer
Esempio n. 2
0
        def connectLane(self, laneIndex):
            ## Access applet operators
            opData = self.dataSelectionApplet.topLevelOperator.getLane(
                laneIndex)
            opFeatureSelection = self.featureSelectionApplet.topLevelOperator.getLane(
                laneIndex)
            opPixelClassification = self.pixelClassificationApplet.topLevelOperator.getLane(
                laneIndex)
            opPreprocessing = self.preprocessingApplet.topLevelOperator.getLane(
                laneIndex)
            opCarvingLane = self.carvingApplet.topLevelOperator.getLane(
                laneIndex)

            op5 = Op5ifyer(parent=self)
            op5.order.setValue("txyzc")
            op5.input.connect(opData.Image)

            ## Connect operators
            opFeatureSelection.InputImage.connect(op5.output)
            opPixelClassification.InputImages.connect(op5.output)
            opPixelClassification.FeatureImages.connect(
                opFeatureSelection.OutputImage)
            opPixelClassification.CachedFeatureImages.connect(
                opFeatureSelection.CachedOutputImage)
            opPixelClassification.LabelsAllowedFlags.connect(
                opData.AllowLabels)

            # We assume the membrane boundaries are found in the first prediction class (channel 0)
            opSingleChannelSelector = OpSingleChannelSelector(parent=self)
            opSingleChannelSelector.Input.connect(
                opPixelClassification.PredictionProbabilities)
            opSingleChannelSelector.Index.setValue(0)

            opPreprocessing.InputData.connect(opSingleChannelSelector.Output)
            opPreprocessing.RawData.connect(op5.output)
            opCarvingLane.RawData.connect(op5.output)
            opCarvingLane.InputData.connect(opSingleChannelSelector.Output)
            opCarvingLane.FilteredInputData.connect(
                opPreprocessing.FilteredImage)

            # Special input-input connection: WriteSeeds metadata must mirror the input data
            opCarvingLane.WriteSeeds.connect(opCarvingLane.InputData)

            opCarvingLane.MST.connect(opPreprocessing.PreprocessedData)
            #opCarvingLane.opLabeling.LabelsAllowedFlag.connect( opData.AllowLabels )
            opCarvingLane.UncertaintyType.setValue("none")

            self.preprocessingApplet.enableDownstream(False)
Esempio n. 3
0
    def __init__(self, *args, **kwargs):
        super(OpThresholdTwoLevels, self).__init__(*args, **kwargs)

        self.opReorderInput = OpReorderAxes(parent=self)
        self.opReorderInput.AxisOrder.setValue('tzyxc')
        self.opReorderInput.Input.connect(self.InputImage)

        # PROBABILITIES: Convert to float32
        self.opConvertProbabilities = OpConvertDtype( parent=self )
        self.opConvertProbabilities.ConversionDtype.setValue( np.float32 )
        self.opConvertProbabilities.Input.connect( self.opReorderInput.Output )

        # PROBABILITIES: Normalize drange to [0.0, 1.0]
        self.opNormalizeProbabilities = OpPixelOperator( parent=self )
        def normalize_inplace(a):
            drange = self.opNormalizeProbabilities.Input.meta.drange
            if drange is None or (drange[0] == 0.0 and drange[1] == 1.0):
                return a
            a[:] -= drange[0]
            a[:] = a[:]/float(( drange[1] - drange[0] ))
            return a
        self.opNormalizeProbabilities.Input.connect( self.opConvertProbabilities.Output )
        self.opNormalizeProbabilities.Function.setValue( normalize_inplace )
        
        self.opSmoother = OpAnisotropicGaussianSmoothing5d(parent=self)
        self.opSmoother.Sigmas.connect( self.SmootherSigma )
        self.opSmoother.Input.connect( self.opNormalizeProbabilities.Output )
        
        self.opSmootherCache = OpBlockedArrayCache(parent=self)
        self.opSmootherCache.BlockShape.setValue((1, None, None, None, 1))
        self.opSmootherCache.Input.connect( self.opSmoother.Output )
        
        self.opCoreChannelSelector = OpSingleChannelSelector(parent=self)
        self.opCoreChannelSelector.Index.connect( self.CoreChannel )
        self.opCoreChannelSelector.Input.connect( self.opSmootherCache.Output )
        
        self.opCoreThreshold = OpLabeledThreshold(parent=self)
        self.opCoreThreshold.Method.setValue( ThresholdMethod.SIMPLE )
        self.opCoreThreshold.FinalThreshold.connect( self.HighThreshold )
        self.opCoreThreshold.Input.connect( self.opCoreChannelSelector.Output )

        self.opCoreFilter = OpFilterLabels(parent=self)
        self.opCoreFilter.BinaryOut.setValue(False)
        self.opCoreFilter.MinLabelSize.connect( self.MinSize )
        self.opCoreFilter.MaxLabelSize.connect( self.MaxSize )
        self.opCoreFilter.Input.connect( self.opCoreThreshold.Output )
        
        self.opFinalChannelSelector = OpSingleChannelSelector(parent=self)
        self.opFinalChannelSelector.Index.connect( self.Channel )
        self.opFinalChannelSelector.Input.connect( self.opSmootherCache.Output )

        self.opSumInputs = OpMultiArrayMerger(parent=self) # see setupOutputs (below) for input connections
        self.opSumInputs.MergingFunction.setValue( sum )
        
        self.opFinalThreshold = OpLabeledThreshold(parent=self)
        self.opFinalThreshold.Method.connect( self.CurOperator )
        self.opFinalThreshold.FinalThreshold.connect( self.LowThreshold )
        self.opFinalThreshold.GraphcutBeta.connect( self.Beta )
        self.opFinalThreshold.CoreLabels.connect( self.opCoreFilter.Output )
        self.opFinalThreshold.Input.connect( self.opSumInputs.Output )
        
        self.opFinalFilter = OpFilterLabels(parent=self)
        self.opFinalFilter.BinaryOut.setValue(False)
        self.opFinalFilter.MinLabelSize.connect( self.MinSize )
        self.opFinalFilter.MaxLabelSize.connect( self.MaxSize )
        self.opFinalFilter.Input.connect( self.opFinalThreshold.Output )

        self.opReorderOutput = OpReorderAxes(parent=self)
        #self.opReorderOutput.AxisOrder.setValue('tzyxc') # See setupOutputs()
        self.opReorderOutput.Input.connect(self.opFinalFilter.Output)

        self.Output.connect( self.opReorderOutput.Output )

        self.opCache = OpBlockedArrayCache(parent=self)
        self.opCache.CompressionEnabled.setValue(True)
        self.opCache.Input.connect( self.opReorderOutput.Output )
        
        self.CachedOutput.connect( self.opCache.Output )
        self.CleanBlocks.connect( self.opCache.CleanBlocks )
        
        ## Debug outputs
        self.Smoothed.connect( self.opSmootherCache.Output )
        self.InputChannel.connect( self.opFinalChannelSelector.Output )
        self.SmallRegions.connect( self.opCoreThreshold.Output )
        self.FilteredSmallLabels.connect( self.opCoreFilter.Output )
        self.BeforeSizeFilter.connect( self.opFinalThreshold.Output )

        # Since hysteresis thresholding creates the big regions and immediately discards the bad ones,
        # we have to recreate it here if the user wants to view it as a debug layer 
        self.opBigRegionsThreshold = OpLabeledThreshold(parent=self)
        self.opBigRegionsThreshold.Method.setValue( ThresholdMethod.SIMPLE )
        self.opBigRegionsThreshold.FinalThreshold.connect( self.LowThreshold )
        self.opBigRegionsThreshold.Input.connect( self.opFinalChannelSelector.Output )
        self.BigRegions.connect( self.opBigRegionsThreshold.Output )
Esempio n. 4
0
    def __init__(self, *args, **kwargs):
        super(OpThresholdTwoLevels, self).__init__(*args, **kwargs)

        self.InputImage.notifyReady(self.checkConstraints)

        self._opReorder1 = OpReorderAxes(parent=self)
        self._opReorder1.AxisOrder.setValue('txyzc')
        self._opReorder1.Input.connect(self.InputImage)

        self._opChannelSelector = OpSingleChannelSelector(parent=self)
        self._opChannelSelector.Input.connect(self._opReorder1.Output)
        self._opChannelSelector.Index.connect(self.Channel)

        # anisotropic gauss
        self._opSmoother = OpAnisotropicGaussianSmoothing5d(parent=self)
        self._opSmoother.Sigmas.connect(self.SmootherSigma)
        self._opSmoother.Input.connect(self._opChannelSelector.Output)

        # debug output
        self.Smoothed.connect(self._opSmoother.Output)

        # single threshold operator
        self.opThreshold1 = _OpThresholdOneLevel(parent=self)
        self.opThreshold1.Threshold.connect(self.SingleThreshold)
        self.opThreshold1.MinSize.connect(self.MinSize)
        self.opThreshold1.MaxSize.connect(self.MaxSize)

        # double threshold operator
        self.opThreshold2 = _OpThresholdTwoLevels(parent=self)
        self.opThreshold2.MinSize.connect(self.MinSize)
        self.opThreshold2.MaxSize.connect(self.MaxSize)
        self.opThreshold2.LowThreshold.connect(self.LowThreshold)
        self.opThreshold2.HighThreshold.connect(self.HighThreshold)

        # Identity-preserving hysteresis thresholding
        self.opIpht = OpIpht(parent=self)
        self.opIpht.MinSize.connect(self.MinSize)
        self.opIpht.MaxSize.connect(self.MaxSize)
        self.opIpht.LowThreshold.connect(self.LowThreshold)
        self.opIpht.HighThreshold.connect(self.HighThreshold)
        self.opIpht.InputImage.connect(self._opSmoother.Output)

        if haveGraphCut():
            self.opThreshold1GC = _OpThresholdOneLevel(parent=self)
            self.opThreshold1GC.Threshold.connect(self.SingleThresholdGC)
            self.opThreshold1GC.MinSize.connect(self.MinSize)
            self.opThreshold1GC.MaxSize.connect(self.MaxSize)

            self.opObjectsGraphCut = OpObjectsSegment(parent=self)
            self.opObjectsGraphCut.Prediction.connect(self.Smoothed)
            self.opObjectsGraphCut.LabelImage.connect(
                self.opThreshold1GC.Output)
            self.opObjectsGraphCut.Beta.connect(self.Beta)
            self.opObjectsGraphCut.Margin.connect(self.Margin)

            self.opGraphCut = OpGraphCut(parent=self)
            self.opGraphCut.Prediction.connect(self.Smoothed)
            self.opGraphCut.Beta.connect(self.Beta)

        self._op5CacheOutput = OpReorderAxes(parent=self)

        self._opReorder2 = OpReorderAxes(parent=self)
        self.Output.connect(self._opReorder2.Output)

        #cache our own output, don't propagate from internal operator
        self._cache = _OpCacheWrapper(parent=self)
        self._cache.name = "OpThresholdTwoLevels.OpCacheWrapper"
        self.CachedOutput.connect(self._cache.Output)

        # Serialization slots
        self._cache.InputHdf5.connect(self.InputHdf5)
        self.CleanBlocks.connect(self._cache.CleanBlocks)
        self.OutputHdf5.connect(self._cache.OutputHdf5)

        #Debug outputs
        self.InputChannel.connect(self._opChannelSelector.Output)
Esempio n. 5
0
    def createStandardLayerFromSlot(self, slot, lastChannelIsAlpha=False):
        """
        Generate a volumina layer using the given slot.
        Choose between grayscale or RGB depending on the number of channels.
        """
        def getRange(meta):
            if 'drange' in meta:
                return meta.drange
            if numpy.issubdtype(meta.dtype, numpy.integer):
                # We assume that ints range up to their max possible value,
                return (0, numpy.iinfo(meta.dtype).max)
            else:
                # If we don't know the range of the data, create a layer that is auto-normalized.
                # See volumina.pixelpipeline.datasources for details.
                return 'autoPercentiles'

        # Examine channel dimension to determine Grayscale vs. RGB
        shape = slot.meta.shape
        normalize = getRange(slot.meta)
        try:
            channelAxisIndex = slot.meta.axistags.index('c')
            #assert channelAxisIndex < len(slot.meta.axistags), \
            #    "slot %s has shape = %r, axistags = %r, but no channel dimension" \
            #    % (slot.name, slot.meta.shape, slot.meta.axistags)
            numChannels = shape[channelAxisIndex]
        except:
            numChannels = 1

        if lastChannelIsAlpha:
            assert numChannels <= 4, "Can't display a standard layer with more than four channels (with alpha).  Your image has {} channels.".format(
                numChannels)
        else:
            assert numChannels <= 3, "Can't display a standard layer with more than three channels (with no alpha).  Your image has {} channels.".format(
                numChannels)

        if numChannels == 1:
            assert not lastChannelIsAlpha, "Can't have an alpha channel if there is no color channel"
            source = LazyflowSource(slot)
            normSource = NormalizingSource(source, bounds=normalize)
            return GrayscaleLayer(normSource)

        assert numChannels > 2 or (numChannels == 2 and not lastChannelIsAlpha)
        redProvider = OpSingleChannelSelector(graph=slot.graph)
        redProvider.Input.connect(slot)
        redProvider.Index.setValue(0)
        redSource = LazyflowSource(redProvider.Output)
        redNormSource = NormalizingSource(redSource, bounds=normalize)

        greenProvider = OpSingleChannelSelector(graph=slot.graph)
        greenProvider.Input.connect(slot)
        greenProvider.Index.setValue(1)
        greenSource = LazyflowSource(greenProvider.Output)
        greenNormSource = NormalizingSource(greenSource, bounds=normalize)

        blueNormSource = None
        if numChannels > 3 or (numChannels == 3 and not lastChannelIsAlpha):
            blueProvider = OpSingleChannelSelector(graph=slot.graph)
            blueProvider.Input.connect(slot)
            blueProvider.Index.setValue(2)
            blueSource = LazyflowSource(blueProvider.Output)
            blueNormSource = NormalizingSource(blueSource, bounds=normalize)

        alphaNormSource = None
        if lastChannelIsAlpha:
            alphaProvider = OpSingleChannelSelector(graph=slot.graph)
            alphaProvider.Input.connect(slot)
            alphaProvider.Index.setValue(numChannels - 1)
            alphaSource = LazyflowSource(alphaProvider.Output)
            alphaNormSource = NormalizingSource(alphaSource, bounds=normalize)

        layer = RGBALayer(red=redNormSource,
                          green=greenNormSource,
                          blue=blueNormSource,
                          alpha=alphaNormSource)
        return layer
Esempio n. 6
0
    def createStandardLayerFromSlot(cls, slot, lastChannelIsAlpha=False):
        """
        Convenience function.
        Generates a volumina layer using the given slot.
        Chooses between grayscale or RGB depending on the number of channels in the slot.

        * If *slot* has 1 channel or more than 4 channels, a GrayscaleLayer is created.
        * If *slot* has 2 non-alpha channels, an RGBALayer is created with R and G channels.
        * If *slot* has 3 non-alpha channels, an RGBALayer is created with R,G, and B channels.
        * If *slot* has 4 channels, an RGBA layer is created

        :param slot: The slot to generate a layer from
        :param lastChannelIsAlpha: If True, the last channel in the slot is assumed to be an alpha channel.
                                   If slot has 4 channels, this parameter has no effect.
        """
        def getRange(meta):
            return meta.drange

        def getNormalize(meta):
            if meta.drange is not None and meta.normalizeDisplay is False:
                # do not normalize if the user provided a range and set normalization to False
                return False
            else:
                # If we don't know the range of the data and normalization is allowed
                # by the user, create a layer that is auto-normalized.
                # See volumina.pixelpipeline.datasources for details.
                #
                # Even in the case of integer data, which has more than 255 possible values,
                # (like uint16), it seems reasonable to use this setting as default
                return None  # means autoNormalize

        shape = slot.meta.shape

        try:
            channelAxisIndex = slot.meta.axistags.index('c')
            #assert channelAxisIndex < len(slot.meta.axistags), \
            #    "slot %s has shape = %r, axistags = %r, but no channel dimension" \
            #    % (slot.name, slot.meta.shape, slot.meta.axistags)
            numChannels = shape[channelAxisIndex]
            axisinfo = slot.meta.axistags["c"].description
        except:
            numChannels = 1
            axisinfo = ""  # == no info on channels given

        rindex = None
        bindex = None
        gindex = None
        aindex = None

        if axisinfo == "" or axisinfo == "default":
            # Examine channel dimension to determine Grayscale vs. RGB

            if numChannels == 4:
                lastChannelIsAlpha = True

            if lastChannelIsAlpha:
                assert numChannels <= 4, "Can't display a standard layer with more than four channels (with alpha).  Your image has {} channels.".format(
                    numChannels)

            if numChannels == 1 or (numChannels > 4):
                assert not lastChannelIsAlpha, "Can't have an alpha channel if there is no color channel"
                source = LazyflowSource(slot)
                layer = GrayscaleLayer(source)
                layer.numberOfChannels = numChannels
                normalize = getNormalize(slot.meta)
                range = getRange(slot.meta)
                layer.set_range(0, range)
                layer.set_normalize(0, normalize)
                return layer

            assert numChannels > 2 or (numChannels == 2 and not lastChannelIsAlpha), \
                "Unhandled combination of channels.  numChannels={}, lastChannelIsAlpha={}, axistags={}".format( numChannels, lastChannelIsAlpha, slot.meta.axistags )

            rindex = 0
            gindex = 1
            if numChannels > 3 or (numChannels == 3
                                   and not lastChannelIsAlpha):
                bindex = 2
            if lastChannelIsAlpha:
                aindex = numChannels - 1

        elif axisinfo == "grayscale":
            source = LazyflowSource(slot)
            layer = GrayscaleLayer(source)
            layer.numberOfChannels = numChannels
            normalize = getNormalize(slot.meta)
            range = getRange(slot.meta)
            layer.set_range(0, range)
            layer.set_normalize(0, normalize)
            return layer

        elif axisinfo == "rgba":
            rindex = 0
            if numChannels >= 2:
                gindex = 1
            if numChannels >= 3:
                bindex = 2
            if numChannels >= 4:
                aindex = numChannels - 1
        else:
            raise RuntimeError("unknown channel display mode")

        redSource = None
        if rindex is not None:
            redProvider = OpSingleChannelSelector(
                parent=slot.getRealOperator().parent)
            redProvider.Input.connect(slot)
            redProvider.Index.setValue(rindex)
            redSource = LazyflowSource(redProvider.Output)
            redSource.additional_owned_ops.append(redProvider)

        greenSource = None
        if gindex is not None:
            greenProvider = OpSingleChannelSelector(
                parent=slot.getRealOperator().parent)
            greenProvider.Input.connect(slot)
            greenProvider.Index.setValue(gindex)
            greenSource = LazyflowSource(greenProvider.Output)
            greenSource.additional_owned_ops.append(greenProvider)

        blueSource = None
        if bindex is not None:
            blueProvider = OpSingleChannelSelector(
                parent=slot.getRealOperator().parent)
            blueProvider.Input.connect(slot)
            blueProvider.Index.setValue(bindex)
            blueSource = LazyflowSource(blueProvider.Output)
            blueSource.additional_owned_ops.append(blueProvider)

        alphaSource = None
        if aindex is not None:
            alphaProvider = OpSingleChannelSelector(
                parent=slot.getRealOperator().parent)
            alphaProvider.Input.connect(slot)
            alphaProvider.Index.setValue(aindex)
            alphaSource = LazyflowSource(alphaProvider.Output)
            alphaSource.additional_owned_ops.append(alphaProvider)

        layer = RGBALayer(red=redSource,
                          green=greenSource,
                          blue=blueSource,
                          alpha=alphaSource)

        normalize = getNormalize(slot.meta)
        range = getRange(slot.meta)
        for i in xrange(4):
            if [redSource, greenSource, blueSource, alphaSource][i]:
                layer.set_range(i, range)
                layer.set_normalize(i, normalize)

        return layer
Esempio n. 7
0
    def __init__(self, *args, **kwargs):
        super(OpThresholdTwoLevels, self).__init__(*args, **kwargs)

        self.opReorderInput = OpReorderAxes(parent=self)
        self.opReorderInput.AxisOrder.setValue('tzyxc')
        self.opReorderInput.Input.connect(self.InputImage)

        self.opSmoother = OpAnisotropicGaussianSmoothing5d(parent=self)
        self.opSmoother.Sigmas.connect(self.SmootherSigma)
        self.opSmoother.Input.connect(self.opReorderInput.Output)

        self.opSmootherCache = OpBlockedArrayCache(parent=self)
        self.opSmootherCache.BlockShape.setValue((1, None, None, None, 1))
        self.opSmootherCache.Input.connect(self.opSmoother.Output)

        self.opCoreChannelSelector = OpSingleChannelSelector(parent=self)
        self.opCoreChannelSelector.Index.connect(self.CoreChannel)
        self.opCoreChannelSelector.Input.connect(self.opSmootherCache.Output)

        self.opCoreThreshold = OpLabeledThreshold(parent=self)
        self.opCoreThreshold.Method.setValue(ThresholdMethod.SIMPLE)
        self.opCoreThreshold.FinalThreshold.connect(self.HighThreshold)
        self.opCoreThreshold.Input.connect(self.opCoreChannelSelector.Output)

        self.opCoreFilter = OpFilterLabels(parent=self)
        self.opCoreFilter.BinaryOut.setValue(False)
        self.opCoreFilter.MinLabelSize.connect(self.MinSize)
        self.opCoreFilter.MaxLabelSize.connect(self.MaxSize)
        self.opCoreFilter.Input.connect(self.opCoreThreshold.Output)

        self.opFinalChannelSelector = OpSingleChannelSelector(parent=self)
        self.opFinalChannelSelector.Index.connect(self.Channel)
        self.opFinalChannelSelector.Input.connect(self.opSmootherCache.Output)

        self.opSumInputs = OpMultiArrayMerger(
            parent=self)  # see setupOutputs (below) for input connections
        self.opSumInputs.MergingFunction.setValue(sum)

        self.opFinalThreshold = OpLabeledThreshold(parent=self)
        self.opFinalThreshold.Method.connect(self.CurOperator)
        self.opFinalThreshold.FinalThreshold.connect(self.LowThreshold)
        self.opFinalThreshold.GraphcutBeta.connect(self.Beta)
        self.opFinalThreshold.CoreLabels.connect(self.opCoreFilter.Output)
        self.opFinalThreshold.Input.connect(self.opSumInputs.Output)

        self.opFinalFilter = OpFilterLabels(parent=self)
        self.opFinalFilter.BinaryOut.setValue(False)
        self.opFinalFilter.MinLabelSize.connect(self.MinSize)
        self.opFinalFilter.MaxLabelSize.connect(self.MaxSize)
        self.opFinalFilter.Input.connect(self.opFinalThreshold.Output)

        self.opReorderOutput = OpReorderAxes(parent=self)
        #self.opReorderOutput.AxisOrder.setValue('tzyxc') # See setupOutputs()
        self.opReorderOutput.Input.connect(self.opFinalFilter.Output)

        self.Output.connect(self.opReorderOutput.Output)

        self.opCache = OpBlockedArrayCache(parent=self)
        self.opCache.CompressionEnabled.setValue(True)
        self.opCache.Input.connect(self.opReorderOutput.Output)

        self.CachedOutput.connect(self.opCache.Output)
        self.CleanBlocks.connect(self.opCache.CleanBlocks)

        ## Debug outputs
        self.Smoothed.connect(self.opSmootherCache.Output)
        self.InputChannel.connect(self.opFinalChannelSelector.Output)
        self.SmallRegions.connect(self.opCoreThreshold.Output)
        self.FilteredSmallLabels.connect(self.opCoreFilter.Output)
        self.BeforeSizeFilter.connect(self.opFinalThreshold.Output)

        # Since hysteresis thresholding creates the big regions and immediately discards the bad ones,
        # we have to recreate it here if the user wants to view it as a debug layer
        self.opBigRegionsThreshold = OpLabeledThreshold(parent=self)
        self.opBigRegionsThreshold.Method.setValue(ThresholdMethod.SIMPLE)
        self.opBigRegionsThreshold.FinalThreshold.connect(self.LowThreshold)
        self.opBigRegionsThreshold.Input.connect(
            self.opFinalChannelSelector.Output)
        self.BigRegions.connect(self.opBigRegionsThreshold.Output)