def expandByShape(self,shape,cIndex,tIndex): """ extend a roi by a given in shape """ #TODO: Warn if bounds are exceeded cStart = self.start[cIndex] cStop = self.stop[cIndex] if tIndex is not None: tStart = self.start[tIndex] tStop = self.stop[tIndex] if isinstance(shape, collections.Iterable): #add a dummy number for the channel dimension shape = shape+(1,) else: tmp = shape shape = numpy.zeros(self.dim).astype(int) shape[:] = tmp tmpStart = [int(x-s) for x,s in zip(self.start,shape)] tmpStop = [int(x+s) for x,s in zip(self.stop,shape)] start = [int(max(t,i)) for t,i in zip(tmpStart,numpy.zeros_like(self.inputShape))] stop = [int(min(t,i)) for t,i in zip(tmpStop,self.inputShape)] start[cIndex] = cStart stop[cIndex] = cStop if tIndex is not None: start[tIndex] = tStart stop[tIndex] = tStop self.start = TinyVector(start) self.stop = TinyVector(stop) return self
def _deserialize(self, mygroup, slot): num = len(mygroup) if len(self.inslot) < num: self.inslot.resize(num) # Annoyingly, some applets store their groups with names like, img0,img1,img2,..,img9,img10,img11 # which means that sorted() needs a special key to avoid sorting img10 before img2 # We have to find the index and sort according to its numerical value. index_capture = re.compile(r"[^0-9]*(\d*).*") def extract_index(s): return int(index_capture.match(s).groups()[0]) for index, t in enumerate( sorted(list(mygroup.items()), key=lambda k_v1: extract_index(k_v1[0]))): groupName, labelGroup = t assert extract_index( groupName ) == index, "subgroup extraction order should be numerical order!" for blockRoiString, blockDataset in list(labelGroup.items()): blockRoi = convertStringToList(blockRoiString) roiShape = TinyVector(blockRoi[1]) - TinyVector(blockRoi[0]) assert roiShape == blockDataset.shape self.inslot[index][roiToSlice(*blockRoi)] = blockDataset
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.statusUpdateSignal` """ # If no data, nothing else is ready. opDataSelection = self.dataSelectionApplet.topLevelOperator input_ready = len(opDataSelection.ImageGroup) > 0 and not self.dataSelectionApplet.busy opFeatureSelection = self.featureSelectionApplet.topLevelOperator featureOutput = opFeatureSelection.OutputImage features_ready = input_ready and \ len(featureOutput) > 0 and \ featureOutput[0].ready() and \ (TinyVector(featureOutput[0].meta.shape) > 0).all() opDataExport = self.dataExportApplet.topLevelOperator predictions_ready = features_ready and \ len(opDataExport.Inputs) > 0 and \ opDataExport.Inputs[0][0].ready() and \ (TinyVector(opDataExport.Inputs[0][0].meta.shape) > 0).all() self._shell.setAppletEnabled(self.featureSelectionApplet, input_ready) self._shell.setAppletEnabled(self.countingApplet, features_ready) self._shell.setAppletEnabled(self.dataExportApplet, predictions_ready and not self.dataExportApplet.busy) self._shell.setAppletEnabled(self.batchProcessingApplet, predictions_ready and not self.batchProcessingApplet.busy) # Lastly, check for certain "busy" conditions, during which we # should prevent the shell from closing the project. busy = False busy |= self.dataSelectionApplet.busy busy |= self.featureSelectionApplet.busy busy |= self.dataExportApplet.busy busy |= self.batchProcessingApplet.busy self._shell.enableProjectChanges( not busy )
def expandByShape(self, shape, cIndex, tIndex): """ extend a roi by a given in shape """ # TODO: Warn if bounds are exceeded cStart = self.start[cIndex] cStop = self.stop[cIndex] if tIndex is not None: tStart = self.start[tIndex] tStop = self.stop[tIndex] if type(shape == int): tmp = shape shape = numpy.zeros(self.dim).astype(int) shape[:] = tmp tmpStart = [int(x - s) for x, s in zip(self.start, shape)] tmpStop = [int(x + s) for x, s in zip(self.stop, shape)] start = [int(max(t, i)) for t, i in zip(tmpStart, numpy.zeros_like(self.inputShape))] stop = [int(min(t, i)) for t, i in zip(tmpStop, self.inputShape)] start[cIndex] = cStart stop[cIndex] = cStop if tIndex is not None: start[tIndex] = tStart stop[tIndex] = tStop self.start = TinyVector(start) self.stop = TinyVector(stop) return self
def __init__(self, slot, start = None, stop = None, pslice = None): super(SubRegion,self).__init__(slot) shape = None if slot is not None: shape = slot.meta.shape if pslice != None or start is not None and stop is None and pslice is None: if pslice is None: pslice = start if shape is None: # Okay to use a shapeless slot if the key is bounded # AND if the key has the correct length assert slicingtools.is_bounded(pslice) # Supply a dummy shape shape = [0] * len(pslice) self.start, self.stop = sliceToRoi(pslice,shape) elif start is None and pslice is None: assert shape is not None, "Can't create a default subregion without a slot and a shape." self.start, self.stop = roiFromShape(shape) else: self.start = TinyVector(start) self.stop = TinyVector(stop) self.dim = len(self.start) for start, stop in zip(self.start, self.stop): assert isinstance(start, (int, long, numpy.integer)), "Roi contains non-integers: {}".format( self ) assert isinstance(start, (int, long, numpy.integer)), "Roi contains non-integers: {}".format( self )
def testBasic(self): start = TinyVector([10, 100, 200, 300, 1]) stop = TinyVector([11, 150, 300, 500, 3]) image_shape = [20, 152, 500, 500, 10] sigma = 3.1 window = 2 enlarge_axes = (False, True, True, True, False) enlarged_start, enlarged_stop = enlargeRoiForHalo( start, stop, image_shape, sigma, window, enlarge_axes) full_halo_width = numpy.ceil(sigma * window) # Non-enlarged axes should remain the same assert (enlarged_start[[0, 4]] == (start[0], start[4])).all(), "{} == {}".format( enlarged_start[[0, 4]], (start[0], start[4])) assert (enlarged_stop[[0, 4]] == (stop[0], stop[4])).all(), "{} == {}".format( enlarged_stop[[0, 4]], (stop[0], stop[4])) # The start coord isn't close to the image border, so the halo should be full-sized on the start side assert (enlarged_start[1:4] == numpy.array(start)[1:4] - full_halo_width).all() # The stop coord is close to the image border in some dimensions, # so some axes couldn't be expanded by the full halo width. assert enlarged_stop[1] == 152 assert enlarged_stop[2] == stop[2] + full_halo_width assert enlarged_stop[3] == 500
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.appletStateUpdateRequested` """ input_ready = self._inputReady(1) cumulated_readyness = input_ready cumulated_readyness &= not self.batchProcessingApplet.busy # Nothing can be touched while batch mode is executing. opFeatureSelection = self.featureSelectionApplet.topLevelOperator featureOutput = opFeatureSelection.OutputImage features_ready = len(featureOutput) > 0 and \ featureOutput[0].ready() and \ (TinyVector(featureOutput[0].meta.shape) > 0).all() cumulated_readyness = cumulated_readyness and features_ready self._shell.setAppletEnabled(self.pcApplet, cumulated_readyness) slot = self.pcApplet.topLevelOperator.PredictionProbabilities predictions_ready = len(slot) > 0 and \ slot[0].ready() and \ (TinyVector(slot[0].meta.shape) > 0).all() cumulated_readyness = cumulated_readyness and predictions_ready self._shell.setAppletEnabled(self.thresholdingApplet, cumulated_readyness) # Problems can occur if the features or input data are changed during live update mode. # Don't let the user do that. opPixelClassification = self.pcApplet.topLevelOperator live_update_active = not opPixelClassification.FreezePredictions.value self._shell.setAppletEnabled(self.dataSelectionApplet, not live_update_active) self._shell.setAppletEnabled(self.featureSelectionApplet, input_ready and not live_update_active) super(ObjectClassificationWorkflowPixel, self).handleAppletStateUpdateRequested(upstream_ready=cumulated_readyness)
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.appletStateUpdateRequested` """ # If no data, nothing else is ready. opDataSelection = self.dataSelectionApplet.topLevelOperator input_ready = len(opDataSelection.ImageGroup ) > 0 and not self.dataSelectionApplet.busy opFeatureSelection = self.featureSelectionApplet.topLevelOperator featureOutput = opFeatureSelection.OutputImage features_ready = input_ready and \ len(featureOutput) > 0 and \ featureOutput[0].ready() and \ (TinyVector(featureOutput[0].meta.shape) > 0).all() opDataExport = self.dataExportApplet.topLevelOperator opPixelClassification = self.pcApplet.topLevelOperator invalid_classifier = opPixelClassification.classifier_cache.fixAtCurrent.value and \ opPixelClassification.classifier_cache.Output.ready() and\ opPixelClassification.classifier_cache.Output.value is None predictions_ready = features_ready and \ not invalid_classifier and \ len(opDataExport.Inputs) > 0 and \ opDataExport.Inputs[0][0].ready() and \ (TinyVector(opDataExport.Inputs[0][0].meta.shape) > 0).all() # Problems can occur if the features or input data are changed during live update mode. # Don't let the user do that. live_update_active = not opPixelClassification.FreezePredictions.value self._shell.setAppletEnabled(self.dataSelectionApplet, not live_update_active) self._shell.setAppletEnabled(self.featureSelectionApplet, input_ready and not live_update_active) self._shell.setAppletEnabled(self.pcApplet, features_ready) self._shell.setAppletEnabled(self.dataExportApplet, predictions_ready) if self.batchInputApplet is not None: # Training workflow must be fully configured before batch can be used self._shell.setAppletEnabled(self.batchInputApplet, predictions_ready) opBatchDataSelection = self.batchInputApplet.topLevelOperator batch_input_ready = predictions_ready and \ len(opBatchDataSelection.ImageGroup) > 0 self._shell.setAppletEnabled(self.batchResultsApplet, batch_input_ready) # Lastly, check for certain "busy" conditions, during which we # should prevent the shell from closing the project. busy = False busy |= self.dataSelectionApplet.busy busy |= self.featureSelectionApplet.busy busy |= self.dataExportApplet.busy self._shell.enableProjectChanges(not busy)
def __setstate__(self, state): """ Support copy.copy() """ self.slot = state['slot'] self.start = TinyVector( state['start'] ) self.stop = TinyVector( state['stop'] ) self.dim = len( state['start'] )
def test_roiToSlice(self): from lazyflow.roi import TinyVector shape = (2, 4, 6, 8, 10) roi = (TinyVector((1, 2, 3, 4, 5)), TinyVector(shape)) assert lazyflow.roi.roiToSlice(roi[0], roi[1]) == (slice(1, 2), slice(2, 4), slice(3, 6), slice(4, 8), slice(5, 10))
def _impl_roi_custom_order(self, axisorder): for i in range(self.tests): config_via_init = bool(i % 2) # Specify a strange order for the output axis tags self.prepareVolnOp(axisorder, len(axisorder) - 1, AxisOrder=axisorder, config_via_init=config_via_init) shape = self.operator.Output.meta.shape roi = [None, None] roi[1] = [ numpy.random.randint(2, s) if s != 1 else 1 for s in shape ] roi[0] = [ numpy.random.randint(0, roi[1][i]) if s != 1 else 0 for i, s in enumerate(shape) ] roi[0] = TinyVector(roi[0]) roi[1] = TinyVector(roi[1]) result = self.operator.Output(roi[0], roi[1]).wait() logger.debug( "------------------------------------------------------") logger.debug("self.array.shape = " + str(self.array.shape)) logger.debug("type(input) == " + str(type(self.operator.Input.value))) logger.debug("input.shape == " + str(self.operator.Input.meta.shape)) logger.debug("Input Tags:") logger.debug(str(self.operator.Input.meta.axistags)) logger.debug("Output Tags:") logger.debug(str(self.operator.Output.meta.axistags)) logger.debug("roi= " + str(roi)) logger.debug("type(result) == " + str(type(result))) logger.debug("result.shape == " + str(result.shape)) logger.debug( "------------------------------------------------------") # Check the shape assert len(result.shape) == len(axisorder) assert not isinstance( result, vigra.VigraArray ), "For compatibility with generic code, output should be provided as a plain numpy array." # Ensure the result came out in the same strange order we asked for. assert self.operator.Output.meta.axistags == vigra.defaultAxistags( axisorder) # Check the data vresult = result.view(vigra.VigraArray) vresult.axistags = self.operator.Output.meta.axistags reorderedInput = self.inArray.withAxes( *[tag.key for tag in self.operator.Output.meta.axistags]) assert numpy.all( vresult == reorderedInput[roiToSlice(roi[0], roi[1])])
def testAny(self): a = TinyVector([True, True, True]) b = TinyVector([True, False, True]) c = TinyVector([False, False, False]) assert a.any() assert b.any() assert not c.any()
def setUp(self): self.v1 = TinyVector(range(1, 11)) self.v2 = TinyVector(range(11, 21)) self.a1 = numpy.array(self.v1) self.a2 = numpy.array(self.v2) self.l1 = list(self.v1) self.l2 = list(self.v2) self.scalar = 3
def setup_method(self, method): self.v1 = TinyVector(list(range(1, 11))) self.v2 = TinyVector(list(range(11, 21))) self.a1 = numpy.array(self.v1) self.a2 = numpy.array(self.v2) self.l1 = list(self.v1) self.l2 = list(self.v2) self.scalar = 3
def test_insert_singleton_axis(self): for i in range(self.tests): self.prepareVolnOp('xyzc', 4) # Specify a strange order for the output axis tags self.operator.AxisOrder.setValue('yxtzc') shape = self.operator.Output.meta.shape roi = [None, None] roi[1] = [ numpy.random.randint(2, s) if s != 1 else 1 for s in shape ] roi[0] = [ numpy.random.randint(0, roi[1][i]) if s != 1 else 0 for i, s in enumerate(shape) ] roi[0] = TinyVector(roi[0]) roi[1] = TinyVector(roi[1]) result = self.operator.Output(roi[0], roi[1]).wait() logger.debug( '------------------------------------------------------') logger.debug("self.array.shape = " + str(self.array.shape)) logger.debug("type(input) == " + str(type(self.operator.Input.value))) logger.debug("input.shape == " + str(self.operator.Input.meta.shape)) logger.debug("Input Tags:") logger.debug(str(self.operator.Input.meta.axistags)) logger.debug("Output Tags:") logger.debug(str(self.operator.Output.meta.axistags)) logger.debug("roi= " + str(roi)) logger.debug("type(result) == " + str(type(result))) logger.debug("result.shape == " + str(result.shape)) logger.debug( '------------------------------------------------------') # Check the shape assert len(result.shape) == 5 assert not isinstance(result, vigra.VigraArray), \ "For compatibility with generic code, output should be provided as a plain numpy array." # Ensure the result came out in the same strange order we asked for. assert self.operator.Output.meta.axistags == vigra.defaultAxistags( 'yxtzc') # Check the data vresult = result.view(vigra.VigraArray) vresult.axistags = self.operator.Output.meta.axistags reorderedInput = self.inArray.withAxes( *[tag.key for tag in self.operator.Output.meta.axistags]) assert numpy.all( vresult == reorderedInput[roiToSlice(roi[0], roi[1])])
def test_Roi_custom_order(self): for i in range(self.tests): self.prepareVolnOp() # Specify a strange order for the output axis tags self.operator.order.setValue('ctyzx') shape = self.operator.outputs["output"].meta.shape roi = [None, None] roi[1] = [ numpy.random.randint(2, s) if s != 1 else 1 for s in shape ] roi[0] = [ numpy.random.randint(0, roi[1][i]) if s != 1 else 0 for i, s in enumerate(shape) ] roi[0] = TinyVector(roi[0]) roi[1] = TinyVector(roi[1]) result = self.operator.outputs["output"](roi[0], roi[1]).wait() logger.debug( '------------------------------------------------------') logger.debug("self.array.shape = " + str(self.array.shape)) logger.debug("type(input) == " + str(type(self.operator.input.value))) logger.debug("input.shape == " + str(self.operator.input.meta.shape)) logger.debug("Input Tags:") logger.debug(str(self.operator.inputs['input'].meta.axistags)) logger.debug("Output Tags:") logger.debug(str(self.operator.output.meta.axistags)) logger.debug("roi= " + str(roi)) logger.debug("type(result) == " + str(type(result))) logger.debug("result.shape == " + str(result.shape)) logger.debug( '------------------------------------------------------') # Check the shape assert len(result.shape) == 5 # Ensure the result came out in the same strange order we asked for. assert self.operator.outputs[ "output"].meta.axistags == vigra.defaultAxistags('ctyzx') # Check the data vresult = result.view(vigra.VigraArray) vresult.axistags = self.operator.outputs[ "output"].meta.axistags reorderedInput = self.inArray.withAxes(*[ tag.key for tag in self.operator.outputs["output"].meta.axistags ]) assert numpy.all( vresult == reorderedInput[roiToSlice(roi[0], roi[1])])
def _getInputComputeRois(self, roi): axiskeys = self.Input.meta.getAxisKeys() spatialkeys = [k for k in axiskeys if k in 'zyx'] sigma = list(map(self._sigmas.get, spatialkeys)) inputSpatialShape = self.Input.meta.getTaggedShape() spatialRoi = (TinyVector(roi.start), TinyVector(roi.stop)) tIndex = None cIndex = None zIndex = None if 'c' in inputSpatialShape: del inputSpatialShape['c'] cIndex = axiskeys.index('c') if 't' in list(inputSpatialShape.keys()): assert inputSpatialShape['t'] == 1 tIndex = axiskeys.index('t') if 'z' in list( inputSpatialShape.keys()) and inputSpatialShape['z'] == 1: #2D image, avoid kernel longer than line exception del inputSpatialShape['z'] zIndex = axiskeys.index('z') indices = [tIndex, cIndex, zIndex] indices = sorted(indices, reverse=True) for ind in indices: if ind: spatialRoi[0].pop(ind) spatialRoi[1].pop(ind) inputSpatialRoi = enlargeRoiForHalo(spatialRoi[0], spatialRoi[1], list(inputSpatialShape.values()), sigma, window=2.0) # Determine the roi within the input data we're going to request inputRoiOffset = spatialRoi[0] - inputSpatialRoi[0] computeRoi = (inputRoiOffset, inputRoiOffset + spatialRoi[1] - spatialRoi[0]) # For some reason, vigra.filters.gaussianSmoothing will raise an exception if this parameter doesn't have the correct integer type. # (for example, if we give it as a numpy.ndarray with dtype=int64, we get an error) computeRoi = (tuple(map(int, computeRoi[0])), tuple(map(int, computeRoi[1]))) inputRoi = (list(inputSpatialRoi[0]), list(inputSpatialRoi[1])) for ind in reversed(indices): if ind: inputRoi[0].insert(ind, 0) inputRoi[1].insert(ind, 1) return inputRoi, computeRoi
def _deserialize(self, mygroup, slot): num = len(mygroup) if len(self.inslot) < num: self.inslot.resize(num) for index, t in enumerate(sorted(mygroup.items())): groupName, labelGroup = t for blockRoiString, blockDataset in labelGroup.items(): blockRoi = eval(blockRoiString) roiShape = TinyVector(blockRoi[1]) - TinyVector(blockRoi[0]) assert roiShape == blockDataset.shape self.inslot[index][roiToSlice(*blockRoi)] = blockDataset
def adjustChannel(self, start, stop, cPerC, cIndex): if cPerC != 1: start = [ start[i] / cPerC if i == cIndex else start[i] for i in range(len(start)) ] stop = [ stop[i] / cPerC + 1 if i == cIndex else stop[i] for i in range(len(stop)) ] start = TinyVector(start) stop = TinyVector(stop) return start, stop
def execute(self, slot, subindex, roi, result): assert slot == self.Output, "Unknown slot: {}".format( slot.name ) radius = self.CrosshairRadius.value points = map(TinyVector, self.PointList.value) result[:] = 0 result_view = result.view(vigra.VigraArray) result_view.axistags = self.Output.meta.axistags result_3d = result_view.withAxes(*'xyz') axiskeys = self.Output.meta.getAxisKeys() roi_start_3d = TinyVector(roi.start) roi_stop_3d = TinyVector(roi.stop) try: roi_start_3d.pop( axiskeys.index('c') ) roi_stop_3d.pop( axiskeys.index('c') ) except ValueError: pass try: roi_start_3d.pop( axiskeys.index('t') ) roi_stop_3d.pop( axiskeys.index('t') ) except ValueError: pass for point3d in points: point3d -= roi_start_3d cross_min = point3d - radius cross_max = point3d + radius+1 # If the cross would be entirely out-of-view, skip it. if (cross_max < [0,0,0]).any() or \ (cross_min >= result_3d.shape).any(): continue cross_min = numpy.maximum(cross_min, (0,0,0)) cross_max = numpy.minimum(cross_max, result_3d.shape) x,y,z = point3d x1,y1,z1 = cross_min x2,y2,z2 = cross_max if 0 <= y < result_3d.shape[1] and 0 <= z < result_3d.shape[2]: result_3d[x1:x2, y, z ] = 1 if 0 <= x < result_3d.shape[0] and 0 <= z < result_3d.shape[2]: result_3d[x, y1:y2, z ] = 1 if 0 <= x < result_3d.shape[0] and 0 <= y < result_3d.shape[1]: result_3d[x, y, z1:z2] = 1 return result
def generateRandomRoi(maxShape,minShape = 0,minWidth = 0): """ for a given shape of any dimension this method returns a roi which is bounded by maxShape and minShape and has the minimum Width in minWidth in all dimensions """ if not minShape: minShape = tuple(numpy.zeros_like(maxShape)) assert len(maxShape) == len(minShape),'Dimensions of Shape do not match!' roi = [[0,0]] while len([x for x in roi if not abs(x[0]-x[1]) < minWidth]) < len(maxShape): roi = [sorted([numpy.random.randint(minDim,maxDim),numpy.random.randint(minDim,maxDim)]) for minDim,maxDim in zip(minShape,maxShape)] roi = [TinyVector([x[0] for x in roi]),TinyVector([x[1] for x in roi])] return roi
def adjustChannel(self, cPerC, cIndex, channelRes): assert sys.version_info.major == 2, "Alert! This function has not been tested "\ "under python 3. Please remove this assetion and be wary of any strange behavior you encounter" if cPerC != 1 and channelRes == 1: start = [ self.start[i] // cPerC if i == cIndex else self.start[i] for i in range(len(self.start)) ] stop = [ self.stop[i] // cPerC + 1 if i == cIndex else self.stop[i] for i in range(len(self.stop)) ] self.start = TinyVector(start) self.stop = TinyVector(stop) elif channelRes > 1: start = [ 0 if i == cIndex else self.start[i] for i in range(len(self.start)) ] stop = [ channelRes if i == cIndex else self.stop[i] for i in range(len(self.stop)) ] self.start = TinyVector(start) self.stop = TinyVector(stop) return self
def execute(self, slot, subindex, roi, result): assert all(roi.stop <= self.Input.meta.shape), "Requested roi {} is too large for this input image of shape {}.".format( roi, self.Input.meta.shape ) # Determine how much input data we'll need, and where the result will be relative to that input roi inputRoi, computeRoi = self._getInputComputeRois(roi) # Obtain the input data with Timer() as resultTimer: data = self.Input( *inputRoi ).wait() logger.debug("Obtaining input data took {} seconds for roi {}".format( resultTimer.seconds(), inputRoi )) xIndex = self.Input.meta.axistags.index('x') yIndex = self.Input.meta.axistags.index('y') zIndex = self.Input.meta.axistags.index('z') if self.Input.meta.axistags.index('z')<len(self.Input.meta.shape) else None cIndex = self.Input.meta.axistags.index('c') if self.Input.meta.axistags.index('c')<len(self.Input.meta.shape) else None # Must be float32 if data.dtype != numpy.float32: data = data.astype(numpy.float32) axiskeys = self.Input.meta.getAxisKeys() spatialkeys = filter( lambda k: k in 'xyz', axiskeys ) # we need to remove a singleton z axis, otherwise we get # 'kernel longer than line' errors reskey = [slice(None, None, None)]*len(self.Input.meta.shape) reskey[cIndex]=0 if zIndex and self.Input.meta.shape[zIndex]==1: removedZ = True data = data.reshape((data.shape[xIndex], data.shape[yIndex])) reskey[zIndex]=0 spatialkeys = filter( lambda k: k in 'xy', axiskeys ) else: removedZ = False sigma = map(self._sigmas.get, spatialkeys) #Check if we need to smooth if any([x < 0.1 for x in sigma]): if removedZ: resultXY = vigra.taggedView(result, axistags="".join(axiskeys)) resultXY = resultXY.withAxes(*'xy') resultXY[:] = data else: result[:] = data return result # Smooth the input data smoothed = vigra.filters.gaussianSmoothing(data, sigma, window_size=2.0, roi=computeRoi, out=result[tuple(reskey)]) # FIXME: Assumes channel is last axis expectedShape = tuple(TinyVector(computeRoi[1]) - TinyVector(computeRoi[0])) assert tuple(smoothed.shape) == expectedShape, "Smoothed data shape {} didn't match expected shape {}".format( smoothed.shape, roi.stop - roi.start ) return result
def adjustChannel(self,cPerC,cIndex,channelRes): assert sys.version_info.major == 2, "Alert! This function has not been tested "\ "under python 3. Please remove this assetion and be wary of any strange behavior you encounter" if cPerC != 1 and channelRes == 1: start = [self.start[i]//cPerC if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [self.stop[i]//cPerC+1 if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) elif channelRes > 1: start = [0 if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [channelRes if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) return self
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.statusUpdateSignal` """ opDataSelection = self.dataSelectionApplet.topLevelOperator input_ready = len(opDataSelection.ImageGroup) > 0 opDataExport = self.dataExportApplet.topLevelOperator export_data_ready = ( input_ready and len(opDataExport.Inputs[0]) > 0 and opDataExport.Inputs[0][0].ready() and (TinyVector(opDataExport.Inputs[0][0].meta.shape) > 0).all() ) self._shell.setAppletEnabled(self.dataSelectionApplet, not self.batchProcessingApplet.busy) self._shell.setAppletEnabled(self.dataExportApplet, export_data_ready and not self.batchProcessingApplet.busy) self._shell.setAppletEnabled(self.batchProcessingApplet, export_data_ready) # Lastly, check for certain "busy" conditions, during which we # should prevent the shell from closing the project. busy = False busy |= self.dataSelectionApplet.busy busy |= self.dataExportApplet.busy busy |= self.batchProcessingApplet.busy self._shell.enableProjectChanges(not busy)
def _executeCleanBlocks(self, destination): """ Execute function for the CleanBlocks output slot, which produces an *unsorted* list of block rois that the cache currently holds. """ # Set difference: clean = existing - dirty clean_block_starts = set(self._cacheFiles.keys()) - self._dirtyBlocks output_shape = self.Output.meta.shape clean_block_rois = list( map(partial(getBlockBounds, output_shape, self._blockshape), clean_block_starts)) results = [] for cbr in clean_block_rois: results.append([TinyVector(cbr[0]), TinyVector(cbr[1])]) destination[0] = results return destination
def _checkUnaryOperation(self, op): v1 = self.v1 nv1 = TinyVector(-x for x in v1) a1 = numpy.array(v1) na1 = numpy.array(nv1) assert all(op(v1) == op(a1)) assert all(op(nv1) == op(na1))
def setupOutputs(self): self._propagate_dirty = self.propagate_dirty.value start = self.inputs["Start"].value stop = self.inputs["Stop"].value assert isinstance(start, tuple) assert isinstance(stop, tuple) assert len(start) == len(self.inputs["Input"].meta.shape) assert len(start) == len(stop) assert (numpy.array(stop)>= numpy.array(start)).all() out_shape = TinyVector(stop) - TinyVector(start) if (out_shape <= 0).any(): self.Output.meta.NOTREADY = True else: self.Output.meta.assignFrom(self.Input.meta) self.Output.meta.shape = tuple(out_shape) if self.Input.meta.drange is not None: self.Output.meta.drange = self.Input.meta.drange
def __init__(self, slot, start = None, stop = None, pslice = None): super(SubRegion,self).__init__(slot) if pslice != None or start is not None and stop is None and pslice is None: if pslice is None: pslice = start shape = self.slot.meta.shape if shape is None: # Okay to use a shapeless slot if the key is bounded # AND if the key has the correct length assert slicingtools.is_bounded(pslice) # Supply a dummy shape shape = [0] * len(pslice) self.start, self.stop = sliceToRoi(pslice,shape) elif start is None and pslice is None: self.start, self.stop = sliceToRoi(slice(None,None,None),self.slot.meta.shape) else: self.start = TinyVector(start) self.stop = TinyVector(stop) self.dim = len(self.start)
def adjustChannel(self,cPerC,cIndex,channelRes): if cPerC != 1 and channelRes == 1: start = [self.start[i]/cPerC if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [self.stop[i]/cPerC+1 if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) elif channelRes > 1: start = [0 if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [channelRes if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) return self
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.appletStateUpdateRequested` """ # If no data, nothing else is ready. opDataSelection = self.dataSelectionApplet.topLevelOperator input_ready = len(opDataSelection.ImageGroup) > 0 and not self.dataSelectionApplet.busy opDLClassification = self.dlClassificationApplet.topLevelOperator opDataExport = self.dataExportApplet.topLevelOperator predictions_ready = ( input_ready and len(opDataExport.Inputs) > 0 and opDataExport.Inputs[0][0].ready() and (TinyVector(opDataExport.Inputs[0][0].meta.shape) > 0).all() ) # Problems can occur if the input data is changed during live update mode. # Don't let the user do that. live_update_active = not opDLClassification.FreezePredictions.value # The user isn't allowed to touch anything while batch processing is running. batch_processing_busy = False # self.batchProcessingApplet.busy self._shell.setAppletEnabled(self.dataSelectionApplet, not live_update_active and not batch_processing_busy) self._shell.setAppletEnabled(self.dlClassificationApplet, input_ready and not batch_processing_busy) self._shell.setAppletEnabled(self.dataExportApplet, predictions_ready and not batch_processing_busy) # if self.batchProcessingApplet is not None: # self._shell.setAppletEnabled(self.batchProcessingApplet, predictions_ready and not batch_processing_busy) # Lastly, check for certain "busy" conditions, during which we # should prevent the shell from closing the project. busy = False busy |= self.dataSelectionApplet.busy busy |= self.dlClassificationApplet.busy busy |= self.dataExportApplet.busy # busy |= self.batchProcessingApplet.busy self._shell.enableProjectChanges(not busy)
class SubRegion(Roi): def __init__(self, slot, start=None, stop=None, pslice=None): super(SubRegion, self).__init__(slot) if pslice != None or start is not None and stop is None and pslice is None: if pslice is None: pslice = start shape = self.slot.meta.shape if shape is None: # Okay to use a shapeless slot if the key is bounded # AND if the key has the correct length assert slicingtools.is_bounded(pslice) # Supply a dummy shape shape = [0] * len(pslice) self.start, self.stop = sliceToRoi(pslice, shape) elif start is None and pslice is None: self.start, self.stop = sliceToRoi(slice(None, None, None), self.slot.meta.shape) else: self.start = TinyVector(start) self.stop = TinyVector(stop) self.dim = len(self.start) def __str__(self): return "".join(("Subregion: start '", str(self.start), "' stop '", str(self.stop), "'")) @staticmethod def _toString(roi): assert isinstance(roi, SubRegion) assert roi.slot is None, "Can't stringify SubRegions with no slot" return "SubRegion(None, {}, {})".format(roi.start, roi.stop) @staticmethod def _fromString(s): return eval(s) def setInputShape(self, inputShape): assert type(inputShape) == tuple self.inputShape = inputShape def copy(self): return copy.copy(self) def popDim(self, dim): """ remove the i'th dimension from the SubRegion works inplace ! """ if dim is not None: self.start.pop(dim) self.stop.pop(dim) return self def setDim(self, dim, start, stop): """ change the subarray at dim, to begin at start and to end at stop """ self.start[dim] = start self.stop[dim] = stop return self def insertDim(self, dim, start, stop, at): """ insert a new dimension before dim. set start to start, stop to stop and the axistags to at """ self.start.insert(0, start) self.stop.insert(0, stop) return self def expandByShape(self, shape, cIndex, tIndex): """ extend a roi by a given in shape """ # TODO: Warn if bounds are exceeded cStart = self.start[cIndex] cStop = self.stop[cIndex] if tIndex is not None: tStart = self.start[tIndex] tStop = self.stop[tIndex] if isinstance(shape, collections.Iterable): # add a dummy number for the channel dimension shape = shape + (1,) else: tmp = shape shape = numpy.zeros(self.dim).astype(int) shape[:] = tmp tmpStart = [int(x - s) for x, s in zip(self.start, shape)] tmpStop = [int(x + s) for x, s in zip(self.stop, shape)] start = [int(max(t, i)) for t, i in zip(tmpStart, numpy.zeros_like(self.inputShape))] stop = [int(min(t, i)) for t, i in zip(tmpStop, self.inputShape)] start[cIndex] = cStart stop[cIndex] = cStop if tIndex is not None: start[tIndex] = tStart stop[tIndex] = tStop self.start = TinyVector(start) self.stop = TinyVector(stop) return self def adjustRoi(self, halo, cIndex=None): if type(halo) != list: halo = [halo] * len(self.start) notAtStartEgde = map(lambda x, y: True if x < y else False, halo, self.start) for i in range(len(notAtStartEgde)): if notAtStartEgde[i]: self.stop[i] = int(self.stop[i] - self.start[i] + halo[i]) self.start[i] = int(halo[i]) return self def adjustChannel(self, cPerC, cIndex, channelRes): if cPerC != 1 and channelRes == 1: start = [self.start[i] / cPerC if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [self.stop[i] / cPerC + 1 if i == cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) elif channelRes > 1: start = [0 if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [channelRes if i == cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) return self def toSlice(self, hardBind=False): return roiToSlice(self.start, self.stop, hardBind)
def _executeProjection2D(self, roi, destination): assert sum(TinyVector(destination.shape) > 1) <= 2, "Projection result must be exactly 2D" # First, we have to determine which axis we are projecting along. # We infer this from the shape of the roi. # For example, if the roi is of shape # zyx = (1,256,256), then we know we're projecting along Z # If more than one axis has a width of 1, then we choose an # axis according to the following priority order: zyxt tagged_input_shape = self.Input.meta.getTaggedShape() tagged_result_shape = collections.OrderedDict( zip( tagged_input_shape.keys(), destination.shape ) ) nonprojection_axes = [] for key in tagged_input_shape.keys(): if (key == 'c' or tagged_input_shape[key] == 1 or tagged_result_shape[key] > 1): nonprojection_axes.append( key ) possible_projection_axes = set(tagged_input_shape) - set(nonprojection_axes) if len(possible_projection_axes) == 0: # If the image is 2D to begin with, # then the projection is simply the same as the normal output, # EXCEPT it is made binary self.Output(roi.start, roi.stop).writeInto(destination).wait() # make binary numpy.greater(destination, 0, out=destination) return for k in 'zyxt': if k in possible_projection_axes: projection_axis_key = k break # Now we know which axis we're projecting along. # Proceed with the projection, working blockwise to avoid unecessary work in unlabeled blocks projection_axis_index = self.Input.meta.getAxisKeys().index(projection_axis_key) projection_length = tagged_input_shape[projection_axis_key] input_roi = roi.copy() input_roi.start[projection_axis_index] = 0 input_roi.stop[projection_axis_index] = projection_length destination[:] = 0.0 # Get the logical blocking. block_starts = getIntersectingBlocks( self._blockshape, (input_roi.start, input_roi.stop) ) # (Parallelism wouldn't help here: h5py will serialize these requests anyway) block_starts = map( tuple, block_starts ) for block_start in block_starts: if block_start not in self._cacheFiles: # No label data in this block. Move on. continue entire_block_roi = getBlockBounds( self.Output.meta.shape, self._blockshape, block_start ) # This block's portion of the roi intersecting_roi = getIntersection( (input_roi.start, input_roi.stop), entire_block_roi ) # Compute slicing within the deep array and slicing within this block deep_relative_intersection = numpy.subtract(intersecting_roi, input_roi.start) block_relative_intersection = numpy.subtract(intersecting_roi, block_start) deep_data = self._getBlockDataset( entire_block_roi )[roiToSlice(*block_relative_intersection)] # make binary and convert to float deep_data_float = numpy.where( deep_data, numpy.float32(1.0), numpy.float32(0.0) ) # multiply by slice-index deep_data_view = numpy.rollaxis(deep_data_float, projection_axis_index, 0) min_deep_slice_index = deep_relative_intersection[0][projection_axis_index] max_deep_slice_index = deep_relative_intersection[1][projection_axis_index] def calc_color_value(slice_index): # Note 1: We assume that the colortable has at least 256 entries in it, # so, we try to ensure that all colors are above 1/256 # (we don't want colors in low slices to be rounded to 0) # Note 2: Ideally, we'd use a min projection in the code below, so that # labels in the "back" slices would appear occluded. But the # min projection would favor 0.0. Instead, we invert the # relationship between color and slice index, do a max projection, # and then re-invert the colors after everything is done. # Hence, this function starts with (1.0 - ...) return (1.0 - (float(slice_index) / projection_length)) * (1.0 - 1.0/255) + 1.0/255.0 min_color_value = calc_color_value(min_deep_slice_index) max_color_value = calc_color_value(max_deep_slice_index) num_slices = max_deep_slice_index - min_deep_slice_index deep_data_view *= numpy.linspace( min_color_value, max_color_value, num=num_slices )\ [ (slice(None),) + (None,)*(deep_data_view.ndim-1) ] # Take the max projection of this block's data. block_max_projection = numpy.amax(deep_data_float, axis=projection_axis_index, keepdims=True) # Merge this block's projection into the overall projection. destination_relative_intersection = numpy.array(deep_relative_intersection) destination_relative_intersection[:, projection_axis_index] = (0,1) destination_subview = destination[roiToSlice(*destination_relative_intersection)] numpy.maximum(block_max_projection, destination_subview, out=destination_subview) # Invert the nonzero pixels so increasing colors correspond to increasing slices. # See comment in calc_color_value(), above. destination_subview[:] = numpy.where(destination_subview, numpy.float32(1.0) - destination_subview, numpy.float32(0.0)) return
class SubRegion(Roi): def __init__(self, slot, start = None, stop = None, pslice = None): super(SubRegion,self).__init__(slot) shape = None if slot is not None: shape = slot.meta.shape if pslice != None or start is not None and stop is None and pslice is None: if pslice is None: pslice = start if shape is None: # Okay to use a shapeless slot if the key is bounded # AND if the key has the correct length assert slicingtools.is_bounded(pslice) # Supply a dummy shape shape = [0] * len(pslice) self.start, self.stop = sliceToRoi(pslice,shape) elif start is None and pslice is None: assert shape is not None, "Can't create a default subregion without a slot and a shape." self.start, self.stop = roiFromShape(shape) else: self.start = TinyVector(start) self.stop = TinyVector(stop) self.dim = len(self.start) for start, stop in zip(self.start, self.stop): assert isinstance(start, (int, long, numpy.integer)), "Roi contains non-integers: {}".format( self ) assert isinstance(start, (int, long, numpy.integer)), "Roi contains non-integers: {}".format( self ) # FIXME: This assertion is good at finding bugs, but it is currently triggered by # the DataExport applet when the output axis order is changed. # # if self.slot is not None self.slot.meta.shape is not None: # assert all(self.stop <= self.slot.meta.shape), \ # "Roi is out of bounds. roi={}, {}.{}.meta.shape={}"\ # .format((self.start, self.stop), slot.getRealOperator().name, slot.name, self.slot.meta.shape) def __setstate__(self, state): """ Support copy.copy() """ self.slot = state['slot'] self.start = TinyVector( state['start'] ) self.stop = TinyVector( state['stop'] ) self.dim = len( state['start'] ) def __str__( self ): return "".join(("Subregion: start '", str(self.start), "' stop '", str(self.stop), "'")) def pprint(self): """pretty-print this object""" ret = "" for a,b in zip(self.start, self.stop): ret += "%d-%d " % (a,b) return ret @staticmethod def _toString(roi): assert isinstance(roi, SubRegion) assert roi.slot is None, "Can't stringify SubRegions with no slot" return "SubRegion(None, {}, {})".format(roi.start, roi.stop) @staticmethod def _fromString(s): return eval(s) def setInputShape(self,inputShape): assert type(inputShape) == tuple self.inputShape = inputShape def copy(self): return copy.copy(self) def popDim(self, dim): """ remove the i'th dimension from the SubRegion works inplace ! """ if dim is not None: self.start.pop(dim) self.stop.pop(dim) return self def setDim(self, dim , start, stop): """ change the subarray at dim, to begin at start and to end at stop """ self.start[dim] = start self.stop[dim] = stop return self def insertDim(self, dim, start, stop): """ insert a new dimension before dim. set start to start, stop to stop and the axistags to at """ self.start = self.start.insert(dim,start) self.stop = self.stop.insert(dim,stop) return self def expandByShape(self,shape,cIndex,tIndex): """ extend a roi by a given in shape """ #TODO: Warn if bounds are exceeded cStart = self.start[cIndex] cStop = self.stop[cIndex] if tIndex is not None: tStart = self.start[tIndex] tStop = self.stop[tIndex] if isinstance(shape, collections.Iterable): #add a dummy number for the channel dimension shape = shape+(1,) else: tmp = shape shape = numpy.zeros(self.dim).astype(int) shape[:] = tmp tmpStart = [int(x-s) for x,s in zip(self.start,shape)] tmpStop = [int(x+s) for x,s in zip(self.stop,shape)] start = [int(max(t,i)) for t,i in zip(tmpStart,numpy.zeros_like(self.inputShape))] stop = [int(min(t,i)) for t,i in zip(tmpStop,self.inputShape)] start[cIndex] = cStart stop[cIndex] = cStop if tIndex is not None: start[tIndex] = tStart stop[tIndex] = tStop self.start = TinyVector(start) self.stop = TinyVector(stop) return self def adjustRoi(self,halo,cIndex=None): if type(halo) != list: halo = [halo]*len(self.start) notAtStartEgde = map(lambda x,y: True if x<y else False,halo,self.start) for i in range(len(notAtStartEgde)): if notAtStartEgde[i]: self.stop[i] = int(self.stop[i]-self.start[i]+halo[i]) self.start[i] = int(halo[i]) return self def adjustChannel(self,cPerC,cIndex,channelRes): if cPerC != 1 and channelRes == 1: start = [self.start[i]/cPerC if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [self.stop[i]/cPerC+1 if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) elif channelRes > 1: start = [0 if i == cIndex else self.start[i] for i in range(len(self.start))] stop = [channelRes if i==cIndex else self.stop[i] for i in range(len(self.stop))] self.start = TinyVector(start) self.stop = TinyVector(stop) return self def toSlice(self, hardBind = False): return roiToSlice(self.start,self.stop, hardBind)
def _parseAnnotationFile(cls, annotation_filepath, body_label_img_slot): """ Returns dict of annotations of the form { coordinate_3d : Annotation } """ try: with open(annotation_filepath) as annotationFile: annotation_json_dict = json.load(annotationFile) except Exception as ex: raise cls.AnnotationParsingException( "Failed to parse your bookmark file. It isn't valid JSON.", ex), None, sys.exc_info()[2] if 'data' not in annotation_json_dict: raise cls.AnnotationParsingException( "Couldn't find the 'data' list in your bookmark file. Giving up." ), None, sys.exc_info()[2] # Before we parse the bookmarks data, locate the substack description # to calculate the z-coordinate offset (see comment about substack coordinates, above) bookmark_dir = os.path.split(annotation_filepath)[0] substack_dir = os.path.split(bookmark_dir)[0] substack_description_path = os.path.join(substack_dir, 'substack.json') try: with open(substack_description_path) as substack_description_file: substack_description_json_dict = json.load( substack_description_file) except Exception as ex: raise cls.AnnotationParsingException( "Failed to parse SUBSTACK", "Attempted to open substack description file:\n {}" "\n but something went wrong. See console output for details. Giving up." .format(substack_description_path)), None, sys.exc_info()[2] # See comment above about why we have to subtract a Z-offset z_offset = substack_description_json_dict[ 'idz1'] - substack_description_json_dict['border'] # Each bookmark is a dict (see example above) annotations = {} bookmarks = annotation_json_dict['data'] for bookmark in bookmarks: if 'text' in bookmark and str( bookmark['text']).lower().find('split') != -1: coord3d = bookmark['location'] coord3d[1] = 520 - coord3d[ 1] # Raveler y-axis is inverted (Raveler substacks are 520 cubes) coord3d[ 2] -= z_offset # See comments above re: substack coordinates coord3d = tuple(coord3d) coord5d = (0, ) + coord3d + (0, ) pos = TinyVector(coord5d) sample_roi = (pos, pos + 1) # For debug purposes, we sometimes load a smaller volume than the original. # Don't import bookmarks that fall outside our volume if (pos < body_label_img_slot.meta.shape).all(): # Sample the label volume to determine the body id (raveler label) label_sample = body_label_img_slot(*sample_roi).wait() annotations[coord3d] = OpParseAnnotations.Annotation( ravelerLabel=label_sample[0, 0, 0, 0, 0], comment=str(bookmark['text'])) return annotations
def handleAppletStateUpdateRequested(self): """ Overridden from Workflow base class Called when an applet has fired the :py:attr:`Applet.appletStateUpdateRequested` """ # If no data, nothing else is ready. opDataSelection = self.dataSelectionApplet.topLevelOperator input_ready = len(opDataSelection.ImageGroup ) > 0 and not self.dataSelectionApplet.busy # First, determine various 'ready' states for each pixel classification stage (features+prediction) StageFlags = collections.namedtuple( "StageFlags", "input_ready features_ready invalid_classifier predictions_ready live_update_active" ) stage_flags = [] for stage_index, (featureSelectionApplet, pcApplet) in enumerate( zip(self.featureSelectionApplets, self.pcApplets)): if stage_index == 0: input_ready = len(opDataSelection.ImageGroup ) > 0 and not self.dataSelectionApplet.busy else: input_ready = stage_flags[stage_index - 1].predictions_ready opFeatureSelection = featureSelectionApplet.topLevelOperator featureOutput = opFeatureSelection.OutputImage features_ready = ( input_ready and len(featureOutput) > 0 and featureOutput[0].ready() and (TinyVector(featureOutput[0].meta.shape) > 0).all()) opPixelClassification = pcApplet.topLevelOperator invalid_classifier = ( opPixelClassification.classifier_cache.fixAtCurrent.value and opPixelClassification.classifier_cache.Output.ready() and opPixelClassification.classifier_cache.Output.value is None) predictions_ready = ( features_ready and not invalid_classifier and len(opPixelClassification.HeadlessPredictionProbabilities) > 0 and opPixelClassification.HeadlessPredictionProbabilities[0]. ready() and (TinyVector( opPixelClassification.HeadlessPredictionProbabilities[0]. meta.shape) > 0).all()) live_update_active = not opPixelClassification.FreezePredictions.value stage_flags += [ StageFlags(input_ready, features_ready, invalid_classifier, predictions_ready, live_update_active) ] opDataExport = self.dataExportApplet.topLevelOperator opPixelClassification = self.pcApplets[0].topLevelOperator # Problems can occur if the features or input data are changed during live update mode. # Don't let the user do that. any_live_update = any(flags.live_update_active for flags in stage_flags) # The user isn't allowed to touch anything while batch processing is running. batch_processing_busy = self.batchProcessingApplet.busy self._shell.setAppletEnabled( self.dataSelectionApplet, not any_live_update and not batch_processing_busy) for stage_index, (featureSelectionApplet, pcApplet) in enumerate( zip(self.featureSelectionApplets, self.pcApplets)): upstream_live_update = any(flags.live_update_active for flags in stage_flags[:stage_index]) this_stage_live_update = stage_flags[ stage_index].live_update_active downstream_live_update = any(flags.live_update_active for flags in stage_flags[stage_index + 1:]) self._shell.setAppletEnabled( featureSelectionApplet, stage_flags[stage_index].input_ready and not this_stage_live_update and not downstream_live_update and not batch_processing_busy, ) self._shell.setAppletEnabled( pcApplet, stage_flags[stage_index].features_ready # and not downstream_live_update \ # Not necessary because live update modes are synced -- labels can't be added in live update. and not batch_processing_busy, ) self._shell.setAppletEnabled( self.dataExportApplet, stage_flags[-1].predictions_ready and not batch_processing_busy) self._shell.setAppletEnabled( self.batchProcessingApplet, predictions_ready and not batch_processing_busy) # Lastly, check for certain "busy" conditions, during which we # should prevent the shell from closing the project. busy = False busy |= self.dataSelectionApplet.busy busy |= any(applet.busy for applet in self.featureSelectionApplets) busy |= self.dataExportApplet.busy busy |= self.batchProcessingApplet.busy self._shell.enableProjectChanges(not busy)