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]) result_types = self.SelectionNames.value path_formatter = DataExportPathFormatter( dataset_info=rawInfo, working_dir=self.WorkingDirectory.value, 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 = path_formatter.format_path(name_format) # 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 = path_formatter.format_path( internal_dataset_format) 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() def run_distributed_export(self, block_roi: Slice5D): return self._opFormattedExport.run_distributed_export(block_roi)