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()
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()