예제 #1
0
class OpDataExport(Operator):
    """
    Top-level operator for the export applet.
    Mostly a simple wrapper for OpProcessedDataExport, but with extra formatting of the export path based on lane attributes.
    """

    TransactionSlot = InputSlot()  # To apply all settings in one 'transaction',
    # disconnect this slot and reconnect it when all slots are ready

    RawData = InputSlot(optional=True)  # Display only
    FormattedRawData = OutputSlot()  # OUTPUT - for overlaying the transformed raw data with the export data.

    # The dataset info for the original dataset (raw data)
    RawDatasetInfo = InputSlot()
    WorkingDirectory = (
        InputSlot()
    )  # Non-absolute paths are relative to this directory.  If not provided, paths must be absolute.

    Inputs = InputSlot(level=1)  # The exportable slots (should all be of the same shape, except for channel)
    InputSelection = InputSlot(value=0)
    SelectionNames = InputSlot()  # A list of names corresponding to the exportable inputs

    # Subregion params
    RegionStart = InputSlot(optional=True)
    RegionStop = InputSlot(optional=True)

    # Normalization params
    InputMin = InputSlot(optional=True)
    InputMax = InputSlot(optional=True)
    ExportMin = InputSlot(optional=True)
    ExportMax = InputSlot(optional=True)

    ExportDtype = InputSlot(optional=True)
    OutputAxisOrder = InputSlot(optional=True)

    # File settings
    OutputFilenameFormat = InputSlot(
        value="{dataset_dir}/{nickname}_{result_type}"
    )  # A format string allowing {dataset_dir} {nickname}, {roi}, {x_start}, {x_stop}, etc.
    OutputInternalPath = InputSlot(value="exported_data")
    OutputFormat = InputSlot(value="hdf5")

    # Only export csv/HDF5 table (don't export volume)
    TableOnlyName = InputSlot(value="Table-Only")
    TableOnly = InputSlot(value=False)

    ExportPath = OutputSlot()  # Location of the saved file after export is complete.

    ConvertedImage = OutputSlot()  # Cropped image, not yet re-ordered (useful for guis)
    ImageToExport = OutputSlot()  # The image that will be exported

    Dirty = OutputSlot()  # Whether or not the result currently matches what's on disk
    FormatSelectionErrorMsg = OutputSlot()

    ALL_FORMATS = OpFormattedDataExport.ALL_FORMATS

    ####
    # Simplified block diagram for actual export data and 'live preview' display:
    #
    #                            --> ExportPath
    #                           /
    # Input -> opFormattedExport --> ImageToExport (live preview)
    #                          |\
    #                          \ --> ConvertedImage
    #                           \
    #                            --> FormatSeletionIsValid

    ####
    # Simplified block diagram for Raw data display:
    #
    # RegionStart --
    #               \
    # RegionStop --> OpRawSubRegionHelper.RawStart -
    #               /                    .RawStop --\
    #              /                                 \
    # RawData ---->---------------------------------> opFormatRaw --> FormattedRawData

    ####
    def __init__(self, *args, **kwargs):
        super(OpDataExport, self).__init__(*args, **kwargs)

        self._opFormattedExport = OpFormattedDataExport(parent=self)
        opFormattedExport = self._opFormattedExport

        # Forward almost all inputs to the 'real' exporter
        opFormattedExport.TransactionSlot.connect(self.TransactionSlot)
        opFormattedExport.RegionStart.connect(self.RegionStart)
        opFormattedExport.RegionStop.connect(self.RegionStop)
        opFormattedExport.InputMin.connect(self.InputMin)
        opFormattedExport.InputMax.connect(self.InputMax)
        opFormattedExport.ExportMin.connect(self.ExportMin)
        opFormattedExport.ExportMax.connect(self.ExportMax)
        opFormattedExport.ExportDtype.connect(self.ExportDtype)
        opFormattedExport.OutputAxisOrder.connect(self.OutputAxisOrder)
        opFormattedExport.OutputFormat.connect(self.OutputFormat)

        self.ConvertedImage.connect(opFormattedExport.ConvertedImage)
        self.ImageToExport.connect(opFormattedExport.ImageToExport)
        self.ExportPath.connect(opFormattedExport.ExportPath)
        self.FormatSelectionErrorMsg.connect(opFormattedExport.FormatSelectionErrorMsg)
        self.progressSignal = opFormattedExport.progressSignal

        self.Dirty.setValue(True)  # Default to Dirty

        # We don't export the raw data, but we connect it to it's own op
        #  so it can be displayed alongside the data to export in the same viewer.
        # This keeps axis order, shape, etc. in sync with the displayed export data.
        # Note that we must not modify the channels of the raw data, so it gets passed through a helper.
        opHelper = OpRawSubRegionHelper(parent=self)
        opHelper.RawImage.connect(self.RawData)
        opHelper.ExportStart.connect(self.RegionStart)
        opHelper.ExportStop.connect(self.RegionStop)

        opFormatRaw = OpFormattedDataExport(parent=self)
        opFormatRaw.TransactionSlot.connect(self.TransactionSlot)
        opFormatRaw.Input.connect(self.RawData)
        opFormatRaw.RegionStart.connect(opHelper.RawStart)
        opFormatRaw.RegionStop.connect(opHelper.RawStop)
        # Don't normalize the raw data.
        # opFormatRaw.InputMin.connect( self.InputMin )
        # opFormatRaw.InputMax.connect( self.InputMax )
        # opFormatRaw.ExportMin.connect( self.ExportMin )
        # opFormatRaw.ExportMax.connect( self.ExportMax )
        # opFormatRaw.ExportDtype.connect( self.ExportDtype )
        opFormatRaw.OutputAxisOrder.connect(self.OutputAxisOrder)
        opFormatRaw.OutputFormat.connect(self.OutputFormat)
        self._opFormatRaw = opFormatRaw
        self.FormattedRawData.connect(opFormatRaw.ImageToExport)

    def setupOutputs(self):
        # FIXME: If RawData becomes unready() at the same time as RawDatasetInfo(), then
        #          we have no guarantees about which one will trigger setupOutputs() first.
        #        It is therefore possible for 'RawDatasetInfo' to appear ready() to us,
        #          even though it's upstream partner is UNready.  We are about to get the
        #          unready() notification, but it will come too late to prevent our
        #          setupOutputs method from being called.
        #        Without proper graph setup transaction semantics, we have to use this
        #          hack as a workaround.
        try:
            rawInfo = self.RawDatasetInfo.value
        except:
            for oslot in list(self.outputs.values()):
                if oslot.upstream_slot is None:
                    oslot.meta.NOTREADY = True
            return

        selection_index = self.InputSelection.value
        if not self.Inputs[selection_index].ready():
            for oslot in list(self.outputs.values()):
                if oslot.upstream_slot is None:
                    oslot.meta.NOTREADY = True
            return
        self._opFormattedExport.Input.connect(self.Inputs[selection_index])

        dataset_dir = str(rawInfo.default_output_dir)
        abs_dataset_dir, _ = getPathVariants(dataset_dir, self.WorkingDirectory.value)
        known_keys = {}
        known_keys["dataset_dir"] = abs_dataset_dir
        nickname = rawInfo.nickname.replace("*", "")
        if os.path.pathsep in nickname:
            nickname = PathComponents(nickname.split(os.path.pathsep)[0]).fileNameBase
        known_keys["nickname"] = nickname
        result_types = self.SelectionNames.value
        known_keys["result_type"] = result_types[selection_index]

        self._opFormattedExport.TransactionSlot.disconnect()

        # Blank the internal path while we manipulate the external path
        #  to avoid invalid intermediate states of ExportPath
        self._opFormattedExport.OutputInternalPath.setValue("")

        # use partial formatting to fill in non-coordinate name fields
        name_format = self.OutputFilenameFormat.value
        partially_formatted_name = format_known_keys(name_format, known_keys)

        # Convert to absolute path before configuring the internal op
        abs_path, _ = getPathVariants(partially_formatted_name, self.WorkingDirectory.value)
        self._opFormattedExport.OutputFilenameFormat.setValue(abs_path)

        # use partial formatting on the internal dataset name, too
        internal_dataset_format = self.OutputInternalPath.value
        partially_formatted_dataset_name = format_known_keys(internal_dataset_format, known_keys)
        self._opFormattedExport.OutputInternalPath.setValue(partially_formatted_dataset_name)

        # Re-connect to finish the 'transaction'
        self._opFormattedExport.TransactionSlot.connect(self.TransactionSlot)

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

    def propagateDirty(self, slot, subindex, roi):
        # Out input data changed, so we have work to do when we get executed.
        self.Dirty.setValue(True)

    def run_export(self):
        # If Table-Only is disabled or we're not dirty, we don't have to do anything.
        if not self.TableOnly.value and self.Dirty.value:
            self._opFormattedExport.run_export()
            self.Dirty.setValue(False)

    def run_export_to_array(self):
        # This function can be used to export the results to an in-memory array, instead of to disk
        # (Typically used from pure-python clients in batch mode.)
        return self._opFormattedExport.run_export_to_array()
예제 #2
0
class OpDataExport(Operator):
    """
    Top-level operator for the export applet.
    Mostly a simple wrapper for OpProcessedDataExport, but with extra formatting of the export path based on lane attributes.
    """
    TransactionSlot = InputSlot()   # To apply all settings in one 'transaction', 
                                    # disconnect this slot and reconnect it when all slots are ready

    RawData = InputSlot(optional=True) # Display only
    FormattedRawData = OutputSlot() # OUTPUT - for overlaying the transformed raw data with the export data.

    # The dataset info for the original dataset (raw data)
    RawDatasetInfo = InputSlot()
    WorkingDirectory = InputSlot() # Non-absolute paths are relative to this directory.  If not provided, paths must be absolute.
    
    Inputs = InputSlot(level=1) # The exportable slots (should all be of the same shape, except for channel)
    InputSelection = InputSlot(value=0)
    SelectionNames = InputSlot() # A list of names corresponding to the exportable inputs

    # Subregion params
    RegionStart = InputSlot(optional=True)
    RegionStop = InputSlot(optional=True)

    # Normalization params    
    InputMin = InputSlot(optional=True)
    InputMax = InputSlot(optional=True)
    ExportMin = InputSlot(optional=True)
    ExportMax = InputSlot(optional=True)

    ExportDtype = InputSlot(optional=True)
    OutputAxisOrder = InputSlot(optional=True)
    
    # File settings
    OutputFilenameFormat = InputSlot(value='{dataset_dir}/{nickname}_{result_type}') # A format string allowing {dataset_dir} {nickname}, {roi}, {x_start}, {x_stop}, etc.
    OutputInternalPath = InputSlot(value='exported_data')
    OutputFormat = InputSlot(value='hdf5')
    
    # Only export csv/HDF5 table (don't export volume)
    TableOnlyName = InputSlot(value='Table-Only')
    TableOnly = InputSlot(value=False)
    
    ExportPath = OutputSlot() # Location of the saved file after export is complete.
    
    ConvertedImage = OutputSlot() # Cropped image, not yet re-ordered (useful for guis)
    ImageToExport = OutputSlot() # The image that will be exported

    ImageOnDisk = OutputSlot() # This slot reads the exported image from disk (after the export is complete)
    Dirty = OutputSlot() # Whether or not the result currently matches what's on disk
    FormatSelectionErrorMsg = OutputSlot()

    ALL_FORMATS = OpFormattedDataExport.ALL_FORMATS

    ####
    # Simplified block diagram for actual export data and 'live preview' display:
    # 
    #                            --> ExportPath
    #                           /
    # Input -> opFormattedExport --> ImageToExport (live preview)
    #                          |\
    #                          \ --> ConvertedImage
    #                           \
    #                            --> FormatSeletionIsValid

    ####
    # Simplified block diagram for Raw data display:
    #
    # RegionStart --
    #               \
    # RegionStop --> OpRawSubRegionHelper.RawStart -
    #               /                    .RawStop --\
    #              /                                 \
    # RawData ---->---------------------------------> opFormatRaw --> FormattedRawData

    ####
    # Simplified block diagram for "on-disk view" of the exported results file:
    #
    # opFormattedExport.ImageToExport (for metadata only) -->
    #                                                        \
    # opFormattedExport.ExportPath -------------------------> opImageOnDiskProvider --> ImageOnDisk

    def __init__(self, *args, **kwargs):
        super( OpDataExport, self ).__init__(*args, **kwargs)
        
        self._opFormattedExport = OpFormattedDataExport( parent=self )
        opFormattedExport = self._opFormattedExport

        # Forward almost all inputs to the 'real' exporter
        opFormattedExport.TransactionSlot.connect( self.TransactionSlot )
        opFormattedExport.RegionStart.connect( self.RegionStart )
        opFormattedExport.RegionStop.connect( self.RegionStop )
        opFormattedExport.InputMin.connect( self.InputMin )
        opFormattedExport.InputMax.connect( self.InputMax )
        opFormattedExport.ExportMin.connect( self.ExportMin )
        opFormattedExport.ExportMax.connect( self.ExportMax )
        opFormattedExport.ExportDtype.connect( self.ExportDtype )
        opFormattedExport.OutputAxisOrder.connect( self.OutputAxisOrder )
        opFormattedExport.OutputFormat.connect( self.OutputFormat )
        
        self.ConvertedImage.connect( opFormattedExport.ConvertedImage )
        self.ImageToExport.connect( opFormattedExport.ImageToExport )
        self.ExportPath.connect( opFormattedExport.ExportPath )
        self.FormatSelectionErrorMsg.connect( opFormattedExport.FormatSelectionErrorMsg )
        self.progressSignal = opFormattedExport.progressSignal

        self.Dirty.setValue(True) # Default to Dirty

        self._opImageOnDiskProvider = None

        # We don't export the raw data, but we connect it to it's own op 
        #  so it can be displayed alongside the data to export in the same viewer.  
        # This keeps axis order, shape, etc. in sync with the displayed export data.
        # Note that we must not modify the channels of the raw data, so it gets passed through a helper.
        opHelper = OpRawSubRegionHelper( parent=self )
        opHelper.RawImage.connect( self.RawData )
        opHelper.ExportStart.connect( self.RegionStart )
        opHelper.ExportStop.connect( self.RegionStop )

        opFormatRaw = OpFormattedDataExport( parent=self )
        opFormatRaw.TransactionSlot.connect( self.TransactionSlot )
        opFormatRaw.Input.connect( self.RawData )
        opFormatRaw.RegionStart.connect( opHelper.RawStart )
        opFormatRaw.RegionStop.connect( opHelper.RawStop )
        # Don't normalize the raw data.
        #opFormatRaw.InputMin.connect( self.InputMin )
        #opFormatRaw.InputMax.connect( self.InputMax )
        #opFormatRaw.ExportMin.connect( self.ExportMin )
        #opFormatRaw.ExportMax.connect( self.ExportMax )
        #opFormatRaw.ExportDtype.connect( self.ExportDtype )
        opFormatRaw.OutputAxisOrder.connect( self.OutputAxisOrder )
        opFormatRaw.OutputFormat.connect( self.OutputFormat )
        self._opFormatRaw = opFormatRaw
        self.FormattedRawData.connect( opFormatRaw.ImageToExport )

    def cleanupOnDiskView(self):
        if self._opImageOnDiskProvider is not None:
            self.ImageOnDisk.disconnect()
            self._opImageOnDiskProvider.cleanUp()
            self._opImageOnDiskProvider = None

    def setupOnDiskView(self):
        # Set up the output that let's us view the exported file
        self._opImageOnDiskProvider = OpImageOnDiskProvider( parent=self )
        self._opImageOnDiskProvider.TransactionSlot.connect( self.TransactionSlot )
        self._opImageOnDiskProvider.Input.connect( self._opFormattedExport.ImageToExport )
        self._opImageOnDiskProvider.WorkingDirectory.connect( self.WorkingDirectory )
        self._opImageOnDiskProvider.DatasetPath.connect( self._opFormattedExport.ExportPath )
        
        # Not permitted to make this connection because we can't connect our own output to a child operator.
        # Instead, dirty state is copied manually into the child op whenever we change it.
        #self._opImageOnDiskProvider.Dirty.connect( self.Dirty )
        self._opImageOnDiskProvider.Dirty.setValue( False )
        
        self.ImageOnDisk.connect( self._opImageOnDiskProvider.Output )
        
    def setupOutputs(self):
        self.cleanupOnDiskView()

        # FIXME: If RawData becomes unready() at the same time as RawDatasetInfo(), then 
        #          we have no guarantees about which one will trigger setupOutputs() first.
        #        It is therefore possible for 'RawDatasetInfo' to appear ready() to us, 
        #          even though it's upstream partner is UNready.  We are about to get the 
        #          unready() notification, but it will come too late to prevent our 
        #          setupOutputs method from being called.
        #        Without proper graph setup transaction semantics, we have to use this 
        #          hack as a workaround.
        try:
            rawInfo = self.RawDatasetInfo.value
        except:
            for oslot in list(self.outputs.values()):
                if oslot.partner is None:
                    oslot.meta.NOTREADY = True
            return

        selection_index = self.InputSelection.value
        if not self.Inputs[selection_index].ready():
            for oslot in list(self.outputs.values()):
                if oslot.partner is None:
                    oslot.meta.NOTREADY = True
            return
        self._opFormattedExport.Input.connect( self.Inputs[selection_index] )

        if os.path.pathsep in rawInfo.filePath:
            first_dataset = rawInfo.filePath.split(os.path.pathsep)[0]
            dataset_dir = PathComponents(first_dataset).externalDirectory
        else:
            dataset_dir = PathComponents(rawInfo.filePath).externalDirectory
        abs_dataset_dir, _ = getPathVariants(dataset_dir, self.WorkingDirectory.value)
        known_keys = {}        
        known_keys['dataset_dir'] = abs_dataset_dir
        nickname = rawInfo.nickname.replace('*', '')
        if os.path.pathsep in nickname:
            nickname = PathComponents(nickname.split(os.path.pathsep)[0]).fileNameBase
        known_keys['nickname'] = nickname
        result_types = self.SelectionNames.value
        known_keys['result_type'] = result_types[selection_index]

        # Disconnect to open the 'transaction'
        if self._opImageOnDiskProvider is not None:
            self._opImageOnDiskProvider.TransactionSlot.disconnect()
        self._opFormattedExport.TransactionSlot.disconnect()

        # Blank the internal path while we manipulate the external path
        #  to avoid invalid intermediate states of ExportPath
        self._opFormattedExport.OutputInternalPath.setValue( "" )

        # use partial formatting to fill in non-coordinate name fields
        name_format = self.OutputFilenameFormat.value
        partially_formatted_name = format_known_keys( name_format, known_keys )
        
        # Convert to absolute path before configuring the internal op
        abs_path, _ = getPathVariants( partially_formatted_name, self.WorkingDirectory.value )
        self._opFormattedExport.OutputFilenameFormat.setValue( abs_path )

        # use partial formatting on the internal dataset name, too
        internal_dataset_format = self.OutputInternalPath.value 
        partially_formatted_dataset_name = format_known_keys( internal_dataset_format, known_keys )
        self._opFormattedExport.OutputInternalPath.setValue( partially_formatted_dataset_name )

        # Re-connect to finish the 'transaction'
        self._opFormattedExport.TransactionSlot.connect( self.TransactionSlot )
        if self._opImageOnDiskProvider is not None:
            self._opImageOnDiskProvider.TransactionSlot.connect( self.TransactionSlot )
        
        self.setupOnDiskView()

    def execute(self, slot, subindex, roi, result):
        assert False, "Shouldn't get here"
    
    def propagateDirty(self, slot, subindex, roi):
        # Out input data changed, so we have work to do when we get executed.
        self.Dirty.setValue(True)
        if self._opImageOnDiskProvider:
            self._opImageOnDiskProvider.Dirty.setValue( False )

    def run_export(self):
        # If Table-Only is disabled or we're not dirty, we don't have to do anything.
        if not self.TableOnly.value and self.Dirty.value:
            self.cleanupOnDiskView()
            self._opFormattedExport.run_export()
            self.Dirty.setValue( False )
            self.setupOnDiskView()
            self._opImageOnDiskProvider.Dirty.setValue( False )

    def run_export_to_array(self):
        # This function can be used to export the results to an in-memory array, instead of to disk
        # (Typically used from pure-python clients in batch mode.)
        return self._opFormattedExport.run_export_to_array()