Source code for lwsspy.ml.labeling.volumelabeler



# External
from typing import Optional
import numpy as np
from matplotlib import colors
import matplotlib.pyplot as plt
from skimage.segmentation import slic

# Internal
from .segmentlabeler import SegmentLabeler


class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))


[docs]class VolumeLabeler:
[docs] def __init__( self, x, y, z, V, labeldict={'moho': 1, '410': 2, '660': 3, 'none': 9999}, labeled: Optional[dict] = None): """This class uses the :class:``lwsspy.ml.labeling.segementlabeler.SegmentLabeler`` to label slices in a volume. After instantiation, use the label method to label a Volumen of single Channel data. Parameters ---------- x : arraylike x vector y : arralike y vector z : arraylike z vector V : arraylike 3D array with data corresponding to vectors X x Y x Z direction. labeldict : dict, optional dictionary with labels, by default {'moho': 1, '410': 2, '660': 3, 'none': 9999} labeled : Optional[dict], optional Optional dictionary with labeled volume. Labeled should contain 4 variables lx, ly, lz, lV, where l{x,y,z} are boolean arrays that where defined as locations for labeled layers. Useful if you need to take a labeling break. Right now there is no way of resuming to an image make sure you finisshe the ones you have started, by default None Notes ----- Things that I have to fix: 1. The volume should be allowed to be 3-channel data 2. Also allow for custom normalizations and colormaps :Authors: Lucas Sawade (lsawade@princeton.edu) :Last Modified: 2021.07.02 00.00 (Lucas Sawade) """ # Variables self.x = x self.y = y self.z = z self.V = V # Dimensions self.nx = len(x) self.ny = len(y) self.nz = len(z) # Label info self.labeldict = labeldict # Load labeled volume, to continue labeling if labeled is not None: self.labeled = labeled else: self.labeled = dict() self.labeled['lx'] = np.zeros(self.nx, dtype=bool) self.labeled['ly'] = np.zeros(self.ny, dtype=bool) self.labeled['lz'] = np.zeros(self.ny, dtype=bool) self.labeled['lV'] = self.labeldict['none'] * \ np.ones(self.V.shape, dtype=int) # Colormap choices self.cmap = plt.get_cmap('seismic') self.norm = MidpointNormalize(midpoint=0, vmin=np.min( self.V), vmax=0.25 * np.max(self.V))
@classmethod def from_volume(self, filename, *args, **kwargs): # Load NPZ file vardict = np.load(filename) # Assign variables x = vardict["x"] y = vardict["y"] z = vardict["z"] V = vardict["data"] return self(x, y, z, V, *args, **kwargs) @classmethod def from_labeled_volume(self, filename, *args, **kwargs): # Load NPZ file vardict = np.load(filename, allow_pickle=True) # Assign variables x = vardict["x"] y = vardict["y"] z = vardict["z"] V = vardict["V"] # Labeled labeled = dict() labeled["lx"] = vardict["lx"] labeled["ly"] = vardict["ly"] labeled["lz"] = vardict["lz"] labeled["lV"] = vardict["lV"] # Label dictionary labeldict = vardict["labeldict"] return self( x, y, z, V, *args, labeled=labeled, labeldict=labeldict, **kwargs) def save(self, outfile): np.savez( outfile, x=self.x, y=self.y, z=self.z, V=self.V, lx=self.labeled['lx'], ly=self.labeled['ly'], lz=self.labeled['lz'], lV=self.labeled['lV'], labeldict=self.labeldict ) def label(self, direction='x', n=20, n_segments=700): """The labeling method enables you to label a ``n`` slices The labeler will automatically pick ``n`` unlabeled slices randomly. The direction let's you decide the normal to the slices. Parameters ---------- direction : str, optional normal to slices, by default 'x' n : int, optional number of slices tto label, by default 20 n_segments : int, optional number of segments to split the image into, by default 700 Raises ------ ValueError [description] """ # Get slice to plot if direction == 'x': tobelabeled = self.__get_randarray__(self.labeled["lx"], n) elif direction == 'y': tobelabeled = self.__get_randarray__(self.labeled["ly"], n) elif direction == 'z': tobelabeled = self.__get_randarray__(self.labeled["lz"], n) else: raise ValueError(f'Direction {direction} not implemented.') for _label_idx in tobelabeled: try: # Get slice to plot if direction == 'x': array = self.V[_label_idx, :, :].T elif direction == 'y': array = self.V[:, _label_idx, :].T elif direction == 'z': array = self.V[:, :, _label_idx] img2 = self.cmap(self.norm(array)) img = img2[:, :, :-1] # Super Pixel segmentation segments = slic( img, n_segments=n_segments, compactness=20, sigma=0, start_label=0) # Label sl = SegmentLabeler(img, segments, labeldict=self.labeldict) # Get slice to plot if direction == 'x': self.labeled["lV"][_label_idx, :, :] = sl.start_labeling().T self.labeled["lx"][_label_idx] = True elif direction == 'y': self.labeled["lV"][:, _label_idx, :] = sl.start_labeling().T self.labeled["ly"][_label_idx] = True elif direction == 'z': self.labeled["lV"][:, :, _label_idx] = sl.start_labeling() self.labeled["lz"][_label_idx] = True except KeyboardInterrupt: break @staticmethod def __get_randarray__(labeled, N): # Get Unlabeled indeces unlabeled_indeces = np.where(~labeled)[0] # Check whether all labeled if len(unlabeled_indeces) == 0: raise ValueError('Nothing to be labeled') elif len(unlabeled_indeces) < N: print('Less to be labeled than indicated.') N = len(unlabeled_indeces) # Choose N times from unlabeled indeces tobelabeled = np.random.choice( unlabeled_indeces, size=N, replace=False) return tobelabeled
if __name__ == "__main__": infile = '/Users/lucassawade/OneDrive/Research/RF/DATA/GLImER/ccps/US_P_0.58_minrad_3D_it_f2_volume.pkl.npz' outfile = 'labeled_volume.npz' S = Segmenter.from_CCPVolume(infile) S.label(direction='y', n=20, n_segments=700) S.save(outfile)