def augment(self, bounding_box): # Get dropped slices and location dropped_slices = 2 * random.randrange(1, self.max_slices // 2) location = random.randrange(dropped_slices, bounding_box.getSize()[1] - dropped_slices) # Get enlarged bounding box edge1, edge2 = bounding_box.getEdges() edge2 = edge2 + Vector(0, dropped_slices, 0) initial_bounding_box = BoundingBox(edge1, edge2) # Get data raw, label = self.getParent().get(initial_bounding_box) # Augment Numpy arrays augmented_raw, augmented_label = self.drop( raw, label, dropped_slices=dropped_slices, location=location) # Convert back into the data format augmented_raw_data = Data(augmented_raw, bounding_box) augmented_label_data = Data(augmented_label, bounding_box) return (augmented_raw_data, augmented_label_data)
def augment(self, bounding_box): # Get error and location error = random.randrange(2, self.max_error) x_len = bounding_box.getSize()[0] location = random.randrange(10, x_len - 10) # Get initial bounding box edge1, edge2 = bounding_box.getEdges() edge2 += Vector(20, error, 0) initial_bounding_box = BoundingBox(edge1, edge2) # Get data raw_data, label_data = self.getParent().get(initial_bounding_box) raw, label = (raw_data.getArray().copy(), label_data.getArray().copy()) augmented_raw, augmented_label = self.stitch(raw, label, location=location, error=error) # Convert to the data format augmented_raw_data = Data(augmented_raw, bounding_box) augmented_label_data = Data(augmented_label, bounding_box) return (augmented_raw_data, augmented_label_data)
def setIterationSize(self, iteration_size): self.iteration_size = BoundingBox(Vector(0, 0, 0), iteration_size.getSize())
class Array: """ A dataset containing a 3D volumetric array """ def __init__(self, array: np.ndarray, bounding_box: BoundingBox = None, iteration_size: BoundingBox = BoundingBox( Vector(0, 0, 0), Vector(128, 128, 32)), stride: Vector = Vector(64, 64, 16)): """ Initializes a volume with a bounding box and iteration parameters :param array: A 3D Numpy array :param bounding_box: The bounding box encompassing the volume :param iteration_size: The bounding box of each data sample in the dataset iterable :param stride: The stride displacement of each data sample in the dataset iterable. The displacement proceeds first from X then to Y then to Z. """ if isinstance(array, np.ndarray): self._setArray(array) elif isinstance(array, BoundingBox): self.createArray(array) else: raise ValueError("array must be an ndarray or a BoundingBox") self.setBoundingBox(bounding_box) self.setIteration(iteration_size=iteration_size, stride=stride) super().__init__() def get(self, bounding_box: BoundingBox) -> Data: """ Requests a data sample from the volume. If the bounding box does not exist, then the method raises a ValueError. :param bounding_box: The bounding box of the request data sample :return: The data sample requested """ if bounding_box.isDisjoint(self.getBoundingBox()): error_string = ("Bounding box must be inside dataset " + "dimensions instead bounding box is {} while " + "the dataset dimensions are {}") error_string = error_string.format(bounding_box, self.getBoundingBox()) raise ValueError(error_string) sub_bounding_box = bounding_box.intersect(self.getBoundingBox()) array = self.getArray(sub_bounding_box) before_pad = bounding_box.getEdges()[0] - sub_bounding_box.getEdges( )[0] after_pad = bounding_box.getEdges()[1] - sub_bounding_box.getEdges()[1] if before_pad != Vector(0, 0, 0) or after_pad != Vector(0, 0, 0): pad_size = tuple( zip(before_pad.getNumpyDim(), after_pad.getNumpyDim())) array = np.pad(array, pad_width=pad_size, mode="constant") return Data(array, bounding_box) def set(self, data: Data): """ Sets a section of the volume within the provided bounding box with the given data. :param data: The data packet to set the volume """ data_bounding_box = data.getBoundingBox() data_array = data.getArray() if not data_bounding_box.isSubset(self.getBoundingBox()): raise ValueError("The bounding box must be a subset of the " " volume") data_edge1, data_edge2 = data_bounding_box.getEdges() array_edge1, array_edge2 = self.getBoundingBox().getEdges() edge1 = data_edge1 - array_edge1 edge2 = data_edge2 - array_edge1 x1, y1, z1 = edge1.getComponents() x2, y2, z2 = edge2.getComponents() self.array[z1:z2, y1:y2, x1:x2] = data_array def blend(self, data: Data): """ Blends a section of the volume within the provided bounding box with the given data by taking the elementwise maximum value. :param data: The data packet to blend into the volume """ array = self.get(data.getBoundingBox()).getArray() array = np.maximum(array, data.getArray()) result = Data(array, data.getBoundingBox()) self.set(result) def getArray(self, bounding_box: BoundingBox = None) -> np.ndarray: """ Retrieves the array contents of the volume. If a bounding box is provided, the subsection is returned. :param bounding_box: The bounding box of a subsection of the volume. If the bounding box is outside of the volume, a ValueError is raised. """ if bounding_box is None: return self.array else: if not bounding_box.isSubset(self.getBoundingBox()): raise ValueError("The bounding box must be a subset" + " of the volume") centered_bounding_box = bounding_box - self.getBoundingBox( ).getEdges()[0] edge1, edge2 = centered_bounding_box.getEdges() x1, y1, z1 = edge1.getComponents() x2, y2, z2 = edge2.getComponents() return self.array[z1:z2, y1:y2, x1:x2] def _setArray(self, array): self.array = array def getBoundingBox(self) -> BoundingBox: """ Retrieves the bounding box of the volume :return: The bounding box of the volume """ return self.bounding_box def setBoundingBox(self, bounding_box: BoundingBox = None, displacement: Vector = None): """ Sets the bounding box of the volume. By default, it sets the bounding box to the volume size :param bounding_box: The bounding box of the volume :param displacement: The displacement of the bounding box from the origin """ if bounding_box is None: self.bounding_box = BoundingBox( Vector(0, 0, 0), Vector(*self.getArray().shape[::-1])) else: self.bounding_box = bounding_box if displacement is not None: self.bounding_box = self.bounding_box + displacement def setIteration(self, iteration_size: BoundingBox, stride: Vector): """ Sets the parameters for iterating through the dataset :param iteration_size: The size of each data sample in the volume :param stride: The displacement of each iteration """ if not isinstance(iteration_size, BoundingBox): error_string = ("iteration_size must have type BoundingBox" + " instead it has type {}") error_string = error_string.format(type(iteration_size)) raise ValueError(error_string) if not isinstance(stride, Vector): raise ValueError("stride must have type Vector") if not iteration_size.isSubset( BoundingBox(Vector(0, 0, 0), self.getBoundingBox().getSize())): raise ValueError("iteration_size must be smaller than volume size") self.setIterationSize(iteration_size) self.setStride(stride) def ceil(x): return int(round(x)) self.element_vec = Vector( *map(lambda L, l, s: ceil((L - l) / s + 1), self.getBoundingBox().getSize().getComponents(), self.iteration_size.getSize().getComponents(), self.stride.getComponents())) self.index = 0 def setIterationSize(self, iteration_size): self.iteration_size = BoundingBox(Vector(0, 0, 0), iteration_size.getSize()) def setStride(self, stride): self.stride = stride def getIterationSize(self): return self.iteration_size def getStride(self): return self.stride def __len__(self): return self.element_vec[0] * self.element_vec[1] * self.element_vec[2] def __getitem__(self, idx): bounding_box = self._indexToBoundingBox(idx) result = self.get(bounding_box) return result def _indexToBoundingBox(self, idx): if idx >= len(self): self.index = 0 raise StopIteration element_vec = np.unravel_index(idx, dims=self.element_vec.getComponents()) element_vec = Vector(*element_vec) bounding_box = self.iteration_size + self.stride * element_vec return bounding_box def __enter__(self): pass def __exit__(self): pass