├── ABC_data ├── __init__.py ├── dataset_evaluation_h5.py └── dataset_h5.py ├── PartNet_data ├── __init__.py └── dataset_h5.py ├── README.md ├── boundary_detection ├── README.md └── train_multi_gpu.py ├── datasets └── README.md ├── docs ├── _config.yml ├── images │ ├── abc_data.png │ ├── geometric_boundaries.png │ ├── partnet_data.png │ ├── semantic_segmentation.png │ └── teaser.png └── index.md ├── evaluation ├── README.md ├── __init__.py └── evaluate_boundary_detection.py ├── models ├── __init__.py ├── dgcnn_part_seg.py └── transform_nets.py ├── semantic_segmentation ├── GCMex │ ├── GCMex.cpp │ ├── GCMex.m │ ├── GCMex.mexa64 │ ├── GCMex.mexw64 │ ├── GCMex.mexw64.pdb │ ├── GCMex_compile.m │ ├── GCMex_test.m │ ├── GC_README.txt │ ├── GCoptimization.cpp │ ├── GCoptimization.h │ ├── LICENSE.TXT │ ├── LinkedBlockList.cpp │ ├── LinkedBlockList.h │ ├── ReadMe.txt │ ├── block.h │ ├── energy.h │ ├── example.cpp │ ├── graph.cpp │ ├── graph.h │ └── maxflow.cpp ├── README.md ├── graphCuts.m ├── pointCloudObject.m ├── point_cloud_seg.m └── semantic_segmentation.py ├── trained_models └── README.md └── utils ├── __init__.py ├── evaluation_utils.py ├── local_frames.py ├── provider.py ├── rotations3D.py └── tf_util.py /ABC_data/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ABC_data/dataset_evaluation_h5.py: -------------------------------------------------------------------------------- 1 | """ Import ABC boundary evaluation dataset """ 2 | 3 | import os 4 | import numpy as np 5 | import h5py 6 | import sys 7 | from collections import OrderedDict 8 | from progressbar import ProgressBar 9 | 10 | # Import custom packages 11 | BASE_DIR = os.path.abspath(os.pardir) 12 | sys.path.append(BASE_DIR) 13 | from utils import rotations3D 14 | 15 | _THRESHOLD_TOL_32 = 2.0 * np.finfo(np.float32).eps 16 | _THRESHOLD_TOL_64 = 2.0 * np.finfo(np.float32).eps 17 | 18 | 19 | def pc_normalize(pc): 20 | """Center points""" 21 | centroid = np.mean(pc, axis=0) 22 | pc = pc - centroid 23 | # Normalize points 24 | m = np.max(np.sqrt(np.sum(pc ** 2, axis=1))) 25 | pc /= np.maximum(m, _THRESHOLD_TOL_64 if m.dtype==np.float64 else _THRESHOLD_TOL_32) 26 | 27 | return pc 28 | 29 | 30 | def point_weights(labels): 31 | """ Non-Boundary point weight = 1 32 | Boundary point weight = #non_boundary_points/#boundary_points""" 33 | n_non_boundaries = np.sum(labels == 0) 34 | n_boundaries = np.sum(labels == 1) 35 | assert (n_non_boundaries + n_boundaries == labels.shape) 36 | weights = np.ones(labels.shape) 37 | weights[labels == 1] = float(n_non_boundaries) / float(np.maximum(n_boundaries, 1)) 38 | 39 | return np.expand_dims(weights.astype(np.float32), axis=1) 40 | 41 | 42 | class Dataset(): 43 | def __init__(self, pc_dataset_dir='', split='', sampling=True, npoints=1000, normalize=True, nrotations=1, 44 | random_rotation=False, rotate_principals_around_normal=False): 45 | 46 | # Check dataset directory 47 | if not os.path.isdir(pc_dataset_dir): 48 | print("'{dataset:s}' missing directory".format(dataset=pc_dataset_dir)) 49 | exit(-1) 50 | 51 | # Point cloud configuration 52 | self.pc_dataset_dir = pc_dataset_dir 53 | self.split = split.lower() 54 | self.npoints = npoints 55 | self.normalize = normalize 56 | self.sampling = sampling # if sampling=True and npoints=10000 -> randomize point cloud data 57 | 58 | # Split check 59 | valid_splits = ["test", "val"] 60 | if not self.split in valid_splits: 61 | print("Error: unknown {split:s}; valid splits: train, test, val".format(split=self.split)) 62 | exit(-1) 63 | 64 | # Read filelist 65 | split_file_list_fn = os.path.join(self.pc_dataset_dir, "{split:s}_files.txt".format(split=self.split)) 66 | if not os.path.isfile(split_file_list_fn): 67 | print("ERROR: {fn:s} does not exists" .format(fn=split_file_list_fn)) 68 | exit(-1) 69 | self.split_file_list_fn = split_file_list_fn 70 | with open(self.split_file_list_fn, "rb") as f: 71 | self.split_file_list = [os.path.join(self.pc_dataset_dir, os.path.basename(line.strip())) for line in f.readlines()] 72 | self.split_file_list.sort(key=lambda x: int(os.path.basename(x).split("-")[-1].split('.')[0])) 73 | 74 | # Read .h5 files 75 | print("Loading {dataset_fn:s} ..." .format(dataset_fn=self.pc_dataset_dir)) 76 | self.points = []; self.seg = []; self.normals = []; self.principal = [] 77 | bar = ProgressBar() 78 | for idx in bar(range(len(self.split_file_list))): 79 | if not os.path.isfile(self.split_file_list[idx]): 80 | print("ERROR: {fn:s} does not exists".format(fn=self.split_file_list[idx])) 81 | exit(-1) 82 | seg_data = h5py.File(self.split_file_list[idx], 'r') 83 | # Load xyz coordinates and boundary labels 84 | points = seg_data['data'][...].astype(np.float32) 85 | seg = seg_data['label_seg'][...].astype(np.float32) 86 | self.points.append(points) 87 | self.seg.append(seg) 88 | self._npoints = len(points[0]) 89 | # Load normals 90 | self.normals.append(seg_data['normals'][...].astype(np.float32)) 91 | self._normals = True 92 | # Load principal directions -> these will be rotated along normal to get random tangent vectors 93 | self.principal.append(seg_data['principal'][...].astype(np.float32)) 94 | if seg_data.attrs.keys(): 95 | # Load model ids 96 | if 'fn' in dir(self): 97 | for fn_idx in range(len(points)): 98 | self.fn.append(seg_data.attrs[str(fn_idx)]) 99 | else: 100 | self.fn = [] 101 | for fn_idx in range(len(points)): 102 | self.fn.append(seg_data.attrs[str(fn_idx)]) 103 | 104 | # Cast lists to np.array() objects 105 | self.points = np.vstack(self.points).astype(np.float32) 106 | self.seg = np.vstack(self.seg).astype(np.float32) 107 | self.normals = np.vstack(self.normals).astype(np.float32) 108 | self.principal = np.vstack(self.principal).astype(np.float32) 109 | 110 | # Augment data for rotation 111 | if nrotations < 1: 112 | print("ERROR: number of rotations should be a positive number") 113 | exit(-1) 114 | self.nrotations = nrotations 115 | self.random_rotation = random_rotation 116 | self.rotate_principal_around_normal = rotate_principals_around_normal 117 | self.pts_idx = np.array([[idx] * nrotations for idx, _ in enumerate(self.points)]) 118 | self.pts_idx = self.pts_idx.reshape((len(self.points) * self.nrotations)) 119 | assert (self.pts_idx.shape[0] / self.nrotations == len(self.points)) 120 | if self.random_rotation: 121 | self.rotate_map = None 122 | else: 123 | self.rotate_map = np.arange(0.0, 360.0, 360.0 / float(self.nrotations), dtype=np.float32) 124 | self.rotate = 0 125 | 126 | if self.normalize: 127 | # Normalize XYZ cartesian coordinates 128 | print("Normalize point clouds ...") 129 | for p_idx, p in enumerate(self.points): 130 | p = pc_normalize(p) 131 | # Check for non-finite values 132 | assert (np.isfinite(p).all()) 133 | self.points[p_idx] = p 134 | print("DONE") 135 | 136 | # Print dataset configuration 137 | print("ABC Dataset Configuration") 138 | print("##############################") 139 | print("Dataset: {dataset:s}" .format(dataset=self.pc_dataset_dir)) 140 | print("Split: {split:s}" .format(split=self.split)) 141 | print("#pts: {nPts:d}". format(nPts=len(self.points))) 142 | print("#rotations: {nRot:d}".format(nRot=self.nrotations)) 143 | print("Random rotation: {random_rotation:s}".format(random_rotation=str(self.random_rotation))) 144 | print("Rotate principal around normal: {rotate_principal_around_normal:s}" 145 | .format(rotate_principal_around_normal=str(self.rotate_principal_around_normal))) 146 | print("Subsample: {subsample:s}".format(subsample=str(self.sampling))) 147 | print("Npoints: {npoints:d}".format(npoints=self.npoints if self.sampling else -1)) 148 | print("Normalize: {normalize:s}".format(normalize=str(self.normalize))) 149 | 150 | # For random rotations 151 | self.rng1 = np.random.RandomState(0) 152 | self.rng2 = np.random.RandomState(1) 153 | 154 | 155 | def __getitem__(self, idx): 156 | 157 | # Get xyz coordinates and boundary labels for the i-th point cloud 158 | pts = self.points[self.pts_idx[idx]] 159 | seg = self.seg[self.pts_idx[idx]] 160 | # Get normals 161 | normals = self.normals[self.pts_idx[idx]] 162 | # Get principal directions 163 | principal = np.copy(self.principal[self.pts_idx[idx]]) 164 | 165 | if self.rotate >= self.nrotations: 166 | self.rotate = 0 167 | if self.random_rotation: 168 | # Rotate 3d points around a random axis 169 | axis = self.rng1.uniform(-1, 1, 3) 170 | angle = self.rng1.rand() * 360.0 171 | R = rotations3D.axisAngleMatrix(angle=angle, axis=axis) 172 | else: 173 | # Rotate 3d points along y-axis 174 | R = rotations3D.eulerAnglesMatrix(thetay=self.rotate_map[self.rotate]) 175 | # If #rotations is 1 and random rotation=False then R = I (identity matrix) 176 | R = R.astype(np.float32) 177 | self.rotate += 1 178 | pts = np.matmul(R, pts.T).T 179 | normals = np.matmul(R, normals.T).T 180 | principal[:, 0:3] = np.matmul(R, principal[:, 0:3].T).T 181 | principal[:, 3:6] = np.matmul(R, principal[:, 3:6].T).T 182 | 183 | # Random rotation of principal directions around normal -> random tangent vectors 184 | if self.rotate_principal_around_normal: 185 | # Principals only for non boundary (surface) points 186 | ind = np.where(seg==0)[0] 187 | angles = self.rng2.rand(normals[ind].shape[0]) * 360.0 188 | R = rotations3D.axisAngleMatrixBatch(angle=angles, rotation_axis=normals[ind]) 189 | principal[:, 0:3] = np.squeeze(np.matmul(R, principal[:, 0:3, np.newaxis])) 190 | principal[:, 3:6] = np.squeeze(np.matmul(R, principal[:, 3:6, np.newaxis])) 191 | 192 | if self.sampling: 193 | # Sample only non boundary (surface) points 194 | ind = np.where(seg == 0)[0] 195 | non_boundary_pts = pts[ind] 196 | # Check if there are enough points to sample 197 | if non_boundary_pts.shape[0] >= self.npoints: 198 | choice = np.random.choice(non_boundary_pts.shape[0], self.npoints, replace=False) 199 | else: 200 | # Sample with replacement 201 | choice = np.random.choice(non_boundary_pts.shape[0], self.npoints, replace=True) 202 | # Sample points 203 | non_boundary_pts = non_boundary_pts[choice] 204 | non_boundary_normals = normals[ind] 205 | non_boundary_normals = non_boundary_normals[choice] 206 | principal = principal[choice] 207 | 208 | # Concatenate non boundary and boundary points and normals 209 | ind = np.where(seg==1)[0] 210 | boundary_pts = pts[ind] 211 | boundary_normals = normals[ind] 212 | pts = np.vstack((non_boundary_pts, boundary_pts)) 213 | normals = np.vstack((non_boundary_normals, boundary_normals)) 214 | seg = np.zeros((pts.shape[0],), dtype=np.float32) 215 | seg[non_boundary_pts.shape[0]:] = 1 216 | 217 | pointcloud = (pts,) 218 | pointcloud += (normals,) 219 | pointcloud += (principal,) 220 | pointcloud += (seg,) 221 | 222 | return pointcloud 223 | 224 | 225 | def __len__(self): 226 | return len(self.pts_idx) 227 | 228 | 229 | def filename(self, idx): 230 | return self.fn[self.pts_idx[idx]] -------------------------------------------------------------------------------- /ABC_data/dataset_h5.py: -------------------------------------------------------------------------------- 1 | """ Import ABC boundary dataset """ 2 | 3 | import os 4 | import numpy as np 5 | import h5py 6 | import sys 7 | from collections import OrderedDict 8 | from progressbar import ProgressBar 9 | 10 | # Import custom packages 11 | BASE_DIR = os.path.abspath(os.pardir) 12 | sys.path.append(BASE_DIR) 13 | from utils import rotations3D 14 | 15 | _THRESHOLD_TOL_32 = 2.0 * np.finfo(np.float32).eps 16 | _THRESHOLD_TOL_64 = 2.0 * np.finfo(np.float32).eps 17 | 18 | 19 | def pc_normalize(pc): 20 | """Center points""" 21 | centroid = np.mean(pc, axis=0) 22 | pc = pc - centroid 23 | # Normalize points 24 | m = np.max(np.sqrt(np.sum(pc ** 2, axis=1))) 25 | pc /= np.maximum(m, _THRESHOLD_TOL_64 if m.dtype==np.float64 else _THRESHOLD_TOL_32) 26 | 27 | return pc 28 | 29 | 30 | def point_weights(labels): 31 | """ Non-Boundary point weight = 1 32 | Boundary point weight = #non_boundary_points/#boundary_points""" 33 | n_non_boundaries = np.sum(labels == 0) 34 | n_boundaries = np.sum(labels == 1) 35 | assert (n_non_boundaries + n_boundaries == labels.shape) 36 | weights = np.ones(labels.shape) 37 | weights[labels == 1] = float(n_non_boundaries) / float(np.maximum(n_boundaries, 1)) 38 | 39 | return np.expand_dims(weights.astype(np.float32), axis=1) 40 | 41 | 42 | class Dataset(): 43 | def __init__(self, pc_dataset_dir='', split='', sampling=True, npoints=10000, normalize=True, nrotations=1, 44 | random_rotation=False, rotate_principals_around_normal=False, return_weights=False): 45 | 46 | # Check dataset directory 47 | if not os.path.isdir(pc_dataset_dir): 48 | print("'{dataset:s}' missing directory".format(dataset=pc_dataset_dir)) 49 | exit(-1) 50 | 51 | # Point cloud configuration 52 | self.pc_dataset_dir = pc_dataset_dir 53 | self.split = split.lower() 54 | self.npoints = npoints 55 | self.normalize = normalize 56 | self.sampling = sampling # if sampling=True and npoints=10000 -> randomize point cloud data 57 | self.return_weights = return_weights 58 | 59 | # Split check 60 | valid_splits = ["train", "test", "val"] 61 | if not self.split in valid_splits: 62 | print("Error: unknown {split:s}; valid splits: train, test, val".format(split=self.split)) 63 | exit(-1) 64 | 65 | # Read filelist 66 | split_file_list_fn = os.path.join(self.pc_dataset_dir, "{split:s}_files.txt".format(split=self.split)) 67 | if not os.path.isfile(split_file_list_fn): 68 | print("ERROR: {fn:s} does not exists" .format(fn=split_file_list_fn)) 69 | exit(-1) 70 | self.split_file_list_fn = split_file_list_fn 71 | with open(self.split_file_list_fn, "rb") as f: 72 | self.split_file_list = [os.path.join(self.pc_dataset_dir, os.path.basename(line.strip())) for line in f.readlines()] 73 | self.split_file_list.sort(key=lambda x: int(os.path.basename(x).split("-")[-1].split('.')[0])) 74 | 75 | # Read .h5 files 76 | print("Loading {dataset_fn:s} ..." .format(dataset_fn=self.pc_dataset_dir)) 77 | self.points = []; self.seg = []; self.normals = []; self.principal = [] 78 | bar = ProgressBar() 79 | for idx in bar(range(len(self.split_file_list))): 80 | if not os.path.isfile(self.split_file_list[idx]): 81 | print("ERROR: {fn:s} does not exists".format(fn=self.split_file_list[idx])) 82 | exit(-1) 83 | seg_data = h5py.File(self.split_file_list[idx], 'r') 84 | # Load xyz coordinates and boundary labels 85 | points = seg_data['data'][...].astype(np.float32) 86 | seg = seg_data['label_seg'][...].astype(np.float32) 87 | self.points.append(points) 88 | self.seg.append(seg) 89 | self._npoints = len(points[0]) 90 | # Load normals 91 | self.normals.append(seg_data['normals'][...].astype(np.float32)) 92 | self._normals = True 93 | # Load principal directions 94 | self.principal.append(seg_data['principal'][...].astype(np.float32)) 95 | if seg_data.attrs.keys(): 96 | # Load model ids 97 | if 'fn' in dir(self): 98 | for fn_idx in range(len(points)): 99 | self.fn.append(seg_data.attrs[str(fn_idx)]) 100 | else: 101 | self.fn = [] 102 | for fn_idx in range(len(points)): 103 | self.fn.append(seg_data.attrs[str(fn_idx)]) 104 | 105 | # Cast lists to np.array() objects 106 | self.points = np.vstack(self.points).astype(np.float32) 107 | self.seg = np.vstack(self.seg).astype(np.float32) 108 | self.normals = np.vstack(self.normals).astype(np.float32) 109 | self.principal = np.vstack(self.principal).astype(np.float32) 110 | 111 | # Augment data for rotation 112 | if nrotations < 1: 113 | print("ERROR: number of rotations should be a positive number") 114 | exit(-1) 115 | self.nrotations = nrotations 116 | self.random_rotation = random_rotation 117 | self.rotate_principal_around_normal = rotate_principals_around_normal 118 | self.pts_idx = np.array([[idx] * nrotations for idx, _ in enumerate(self.points)]) 119 | self.pts_idx = self.pts_idx.reshape((len(self.points) * self.nrotations)) 120 | assert (self.pts_idx.shape[0] / self.nrotations == len(self.points)) 121 | if self.random_rotation: 122 | self.rotate_map = None 123 | else: 124 | self.rotate_map = np.arange(0.0, 360.0, 360.0 / float(self.nrotations), dtype=np.float32) 125 | self.rotate = 0 126 | 127 | if self.normalize: 128 | # Normalize XYZ cartesian coordinates 129 | print("Normalize point clouds ...") 130 | for p_idx, p in enumerate(self.points): 131 | p = pc_normalize(p) 132 | # Check for non-finite values 133 | assert (np.isfinite(p).all()) 134 | self.points[p_idx] = p 135 | print("DONE") 136 | 137 | # Print dataset configuration 138 | print("ABC Dataset Configuration") 139 | print("##############################") 140 | print("Dataset: {dataset:s}" .format(dataset=self.pc_dataset_dir)) 141 | print("Split: {split:s}" .format(split=self.split)) 142 | print("#pts: {nPts:d}". format(nPts=len(self.points))) 143 | print("#rotations: {nRot:d}".format(nRot=self.nrotations)) 144 | print("Random rotation: {random_rotation:s}".format(random_rotation=str(self.random_rotation))) 145 | print("Rotate principal around normal: {rotate_principal_around_normal:s}" 146 | .format(rotate_principal_around_normal=str(self.rotate_principal_around_normal))) 147 | print("Subsample: {subsample:s}".format(subsample=str(self.sampling))) 148 | print("Npoints: {npoints:d}".format(npoints=self.npoints if self.sampling else -1)) 149 | print("Normalize: {normalize:s}".format(normalize=str(self.normalize))) 150 | print("Return weights: {ret_weights:s}".format(ret_weights=str(self.return_weights))) 151 | 152 | 153 | def __getitem__(self, idx): 154 | 155 | # Get xyz coordinates and boundary labels for the i-th point cloud 156 | pts = self.points[self.pts_idx[idx]] 157 | seg = self.seg[self.pts_idx[idx]] 158 | # Get normals 159 | normals = self.normals[self.pts_idx[idx]] 160 | # Get principal directions 161 | principal = np.copy(self.principal[self.pts_idx[idx]]) 162 | 163 | if self.rotate >= self.nrotations: 164 | self.rotate = 0 165 | if self.random_rotation: 166 | axis = np.random.uniform(-1, 1, 3) 167 | angle = np.random.rand() * 360.0 168 | R = rotations3D.axisAngleMatrix(angle=angle, axis=axis) 169 | else: 170 | # Rotate 3d points along y-axis 171 | R = rotations3D.eulerAnglesMatrix(thetay=self.rotate_map[self.rotate]) 172 | # If #rotations is 1 and random rotation=False then R = I (identity matrix) 173 | R = R.astype(np.float32) 174 | self.rotate += 1 175 | pts = np.matmul(R, pts.T).T 176 | normals = np.matmul(R, normals.T).T 177 | principal[:, 0:3] = np.matmul(R, principal[:, 0:3].T).T 178 | principal[:, 3:6] = np.matmul(R, principal[:, 3:6].T).T 179 | 180 | # Random rotation of principal directions around normal -> random tangent vectors 181 | if self.rotate_principal_around_normal: 182 | angles = np.random.rand(normals.shape[0]) * 360.0 183 | R = rotations3D.axisAngleMatrixBatch(angle=angles, rotation_axis=normals) 184 | principal[:, 0:3] = np.squeeze(np.matmul(R, principal[:, 0:3, np.newaxis])) 185 | principal[:, 3:6] = np.squeeze(np.matmul(R, principal[:, 3:6, np.newaxis])) 186 | 187 | if self.sampling: 188 | # Check if there are enough points to sample 189 | if pts.shape[0] >= self.npoints: 190 | choice = np.random.choice(pts.shape[0], self.npoints, replace=False) 191 | else: 192 | # Sample with replacement 193 | choice = np.random.choice(pts.shape[0], self.npoints, replace=True) 194 | # Sample points 195 | pts = pts[choice] 196 | seg = seg[choice] 197 | normals = normals[choice] 198 | principal = principal[choice] 199 | 200 | pointcloud = (pts,) 201 | pointcloud += (normals,) 202 | pointcloud += (principal,) 203 | pointcloud += (seg,) 204 | if self.return_weights: # Add weights 205 | # Calculate points weigths w.r.t the ratio of non-boundary over boundary points 206 | pointcloud += (point_weights(seg),) 207 | 208 | return pointcloud 209 | 210 | 211 | def __len__(self): 212 | return len(self.pts_idx) 213 | 214 | 215 | def filename(self, idx): 216 | return self.fn[self.pts_idx[idx]] 217 | -------------------------------------------------------------------------------- /PartNet_data/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /PartNet_data/dataset_h5.py: -------------------------------------------------------------------------------- 1 | """ Import PartNet semantic and boundary segmentation dataset """ 2 | 3 | import os 4 | import numpy as np 5 | import h5py 6 | import sys 7 | from collections import OrderedDict 8 | from progressbar import ProgressBar 9 | 10 | 11 | BASE_DIR = os.path.abspath(os.pardir) 12 | sys.path.append(BASE_DIR) 13 | from utils import rotations3D 14 | 15 | _THRESHOLD_TOL_32 = 2.0 * np.finfo(np.float32).eps 16 | _THRESHOLD_TOL_64 = 2.0 * np.finfo(np.float32).eps 17 | 18 | 19 | # PartNet categories 20 | categories = ["Bag", "Bed", "Bottle", "Bowl", "Chair", "Clock", "Dishwasher", "Display", "Door", "Earphone", "Faucet", 21 | "Hat", "Keyboard", "Knife", "Lamp", "Laptop", "Microwave", "Mug", "Refrigerator", "Scissors", 22 | "StorageFurniture", "Table", "TrashCan", "Vase"] 23 | categories_dict = dict(zip(categories, range(len(categories)))) 24 | 25 | 26 | def pc_normalize(pc): 27 | """Center points""" 28 | centroid = np.mean(pc, axis=0) 29 | pc = pc - centroid 30 | # Normalize points 31 | m = np.max(np.sqrt(np.sum(pc ** 2, axis=1))) 32 | pc /= np.maximum(m, _THRESHOLD_TOL_64 if m.dtype==np.float64 else _THRESHOLD_TOL_32) 33 | 34 | return pc 35 | 36 | 37 | def point_weights(labels): 38 | """ Non-Boundary point weight = 1 39 | Boundary point weight = #non_boundary_points/#boundary_points""" 40 | nNonBoundaries = np.sum(labels == 0) 41 | nBoundaries = np.sum(labels == 1) 42 | assert (nNonBoundaries + nBoundaries == labels.shape) 43 | weights = np.ones(labels.shape) 44 | weights[labels == 1] = float(nNonBoundaries) / float(np.maximum(nBoundaries, 1)) 45 | 46 | return np.expand_dims(weights.astype(np.float32), axis=1) 47 | 48 | 49 | 50 | class Dataset(): 51 | def __init__(self, pc_dataset_dir='', split='', sampling=True, npoints=10000, normalize=True, nrotations=1, 52 | random_rotation=False, rotate_principals_around_normal=False, category='', return_cat=False, return_weights=False, 53 | evaluate=False): 54 | 55 | # Check dataset directory 56 | if not os.path.isdir(pc_dataset_dir): 57 | print("'{dataset:s}' missing directory".format(dataset=pc_dataset_dir)) 58 | exit(-1) 59 | 60 | # Point cloud configuration 61 | self.pc_dataset_dir = pc_dataset_dir 62 | self.split = split.lower() 63 | self.npoints = npoints 64 | self.normalize = normalize 65 | self.sampling = sampling # if sampling=True and npoints=10000 -> randomize point cloud data 66 | self.return_cat = return_cat 67 | self.return_weights = return_weights 68 | self.category = category 69 | 70 | # Check if given category exists and specify level -> only fine-grained semantic levels of PartNet are used 71 | cat_directories = os.listdir(self.pc_dataset_dir) 72 | cat_found = False 73 | for cat_fn in cat_directories: 74 | if cat_fn.split('-')[0].lower() == self.category.lower(): 75 | CATEGORY_DIR = os.path.join(self.pc_dataset_dir, cat_fn) 76 | self.level = int(cat_fn.split('-')[-1]) 77 | cat_found = True 78 | if not cat_found: 79 | print("ERROR: {cat:s} is not part of PartNet dataset".format(cat=self.category)) 80 | exit(-1) 81 | self.CATEGORY_DIR = CATEGORY_DIR 82 | 83 | # Split check 84 | validSplits = ["train", "test", "val"] 85 | if not self.split in validSplits: 86 | print("Error: unknown {split:s}; valid splits: train, test, val".format(split=self.split)) 87 | exit(-1) 88 | 89 | # Read filelist 90 | splitFileListFN = os.path.join(self.CATEGORY_DIR, "{split:s}_files.txt".format(split=self.split)) 91 | if not os.path.isfile(splitFileListFN): 92 | print("ERROR: {fn:s} does not exists" .format(fn=splitFileListFN)) 93 | exit(-1) 94 | self.splitFileListFN = splitFileListFN 95 | with open(self.splitFileListFN, "rb") as f: 96 | self.splitFilelist = [os.path.join(CATEGORY_DIR, os.path.basename(line.strip())) for line in f.readlines()] 97 | self.splitFilelist.sort(key=lambda x: int(os.path.basename(x).split("-")[-1].split('.')[0])) 98 | 99 | # Read .h5 files 100 | print("Loading {datasetFN:s} ..." .format(datasetFN=self.pc_dataset_dir)) 101 | self.points = []; self.seg = []; self.normals = []; self.principal = [] 102 | bar = ProgressBar() 103 | for idx in bar(range(len(self.splitFilelist))): 104 | if not os.path.isfile(self.splitFilelist[idx]): 105 | print("ERROR: {fn:s} does not exists".format(fn=self.splitFilelist[idx])) 106 | exit(-1) 107 | segData = h5py.File(self.splitFilelist[idx], 'r') 108 | points = segData['data'][...].astype(np.float32) 109 | seg = segData['label_seg'][...].astype(np.float32) 110 | self.points.append(points) 111 | self.seg.append(seg) 112 | self._npoints = len(points[0]) 113 | self.normals.append(segData['normals'][...].astype(np.float32)) 114 | self._normals = True 115 | self.principal.append(segData['principal'][...].astype(np.float32)) 116 | self._principal = True 117 | if segData.attrs.keys(): 118 | if 'fn' in dir(self): 119 | for idx in range(len(points)): 120 | self.fn.append(segData.attrs[str(idx)]) 121 | else: 122 | self.fn = [] 123 | for idx in range(len(points)): 124 | self.fn.append(segData.attrs[str(idx)]) 125 | 126 | self.points = np.vstack(self.points) 127 | self.seg = np.vstack(self.seg) 128 | self.normals = np.vstack(self.normals) 129 | self.principal = np.vstack(self.principal) 130 | self.segClasses = OrderedDict({self.category : range(np.amin(self.seg).astype(np.int32), np.amax(self.seg).astype(np.int32)+1)}) 131 | 132 | # Augment data for rotation 133 | if nrotations < 1: 134 | print("ERROR: number of rotations should be a positive number") 135 | exit(-1) 136 | self.nrotations = nrotations 137 | self.random_rotation = random_rotation 138 | self.rotate_principal_around_normal = rotate_principals_around_normal 139 | self.ptsIdx = np.array([[idx] * nrotations for idx, _ in enumerate(self.points)]) 140 | self.ptsIdx = self.ptsIdx.reshape((len(self.points) * self.nrotations)) 141 | assert (self.ptsIdx.shape[0] / self.nrotations == len(self.points)) 142 | if self.random_rotation: 143 | self.rotateMap = None 144 | else: 145 | self.rotateMap = np.arange(0.0, 360.0, 360.0 / float(self.nrotations), dtype=np.float32) 146 | self.rotate = 0 147 | 148 | # Print dataset configuration 149 | print("PartNet Dataset Configuration") 150 | print("##############################") 151 | print("Dataset: {dataset:s}" .format(dataset=self.pc_dataset_dir)) 152 | print("Category: {category:s}" .format(category=self.category)) 153 | print("Hierarchy level: {level:d}".format(level=self.level)) 154 | print("Split: {split:s}" .format(split=self.split)) 155 | print("#pts: {nPts:d}". format(nPts=len(self.points))) 156 | print("#rotations: {nRot:d}".format(nRot=self.nrotations)) 157 | print("Random rotation: {random_rotation:s}".format(random_rotation=str(self.random_rotation))) 158 | print("Rotate principal around normal: {rotate_principal_around_normal:s}" 159 | .format(rotate_principal_around_normal=str(self.rotate_principal_around_normal))) 160 | print("Subsample: {subsample:s}".format(subsample=str(self.sampling))) 161 | print("Npoints: {npoints:d}".format(npoints=self.npoints if self.sampling else self._npoints)) 162 | print("Normalize: {normalize:s}".format(normalize=str(self.normalize))) 163 | print("Return category: {retCat:s}".format(retCat=str(self.return_cat))) 164 | print("Return weights: {retWeights:s}".format(retWeights=str(self.return_weights))) 165 | 166 | self.evaluate = evaluate 167 | if evaluate: 168 | # For random rotations 169 | self.rng1 = np.random.RandomState(categories_dict[category]) 170 | self.rng2 = np.random.RandomState(categories_dict[category]+len(categories_dict)) 171 | 172 | 173 | def __getitem__(self, idx): 174 | 175 | pts = self.points[self.ptsIdx[idx]] 176 | seg = self.seg[self.ptsIdx[idx]] 177 | normals = self.normals[self.ptsIdx[idx]] 178 | principal = np.copy(self.principal[self.ptsIdx[idx]]) 179 | 180 | if self.normalize: 181 | # Normalize XYZ cartesian coordinates 182 | pts = pc_normalize(pts) 183 | # Check for non-finite values 184 | assert (np.isfinite(pts).all()) 185 | 186 | if self.rotate >= self.nrotations: 187 | self.rotate = 0 188 | if self.random_rotation: 189 | # Rotate 3d points around a random axis 190 | if self.evaluate: 191 | axis = self.rng2.uniform(-1, 1, 3) 192 | angle = self.rng2.rand() * 360.0 193 | else: 194 | axis = np.random.uniform(-1, 1, 3) 195 | angle = np.random.rand() * 360.0 196 | R = rotations3D.axisAngleMatrix(angle=angle, axis=axis) 197 | else: 198 | # Rotate 3d points along y-axis 199 | R = rotations3D.eulerAnglesMatrix(thetay=self.rotateMap[self.rotate]) 200 | # If #rotations is 1 and random rotation=False then R = I (identity matrix) 201 | self.rotate += 1 202 | pts = np.matmul(R, pts.T).T 203 | if self._normals: 204 | normals = np.matmul(R, normals.T).T 205 | if self._principal: 206 | principal[:, 0:3] = np.matmul(R, principal[:, 0:3].T).T 207 | principal[:, 3:6] = np.matmul(R, principal[:, 3:6].T).T 208 | 209 | # Random rotation of principal directions around normal -> random tangent vectors 210 | if self.rotate_principal_around_normal: 211 | if self.evaluate: 212 | angles = self.rng1.rand(normals.shape[0]) * 360.0 213 | else: 214 | angles = np.random.rand(normals.shape[0]) * 360.0 215 | R = rotations3D.axisAngleMatrixBatch(angle=angles, rotation_axis=normals) 216 | principal[:, 0:3] = np.squeeze(np.matmul(R, principal[:, 0:3, np.newaxis])) 217 | principal[:, 3:6] = np.squeeze(np.matmul(R, principal[:, 3:6, np.newaxis])) 218 | 219 | if self.sampling: 220 | # Check if there are enough points to sample 221 | if pts.shape[0] >= self.npoints: 222 | choice = np.random.choice(pts.shape[0], self.npoints, replace=False) 223 | else: 224 | # Sample with replacement 225 | choice = np.random.choice(pts.shape[0], self.npoints, replace=True) 226 | # Sample points 227 | pts = pts[choice] 228 | seg = seg[choice] 229 | normals = normals[choice] 230 | principal = principal[choice] 231 | 232 | pointcloud = (pts,) 233 | pointcloud += (normals,) 234 | pointcloud += (principal,) 235 | pointcloud += (seg,) 236 | if self.return_weights: # Add weights 237 | # Calculate points weigths w.r.t the ratio of non-boundary over boundary points 238 | pointcloud += (point_weights(seg),) 239 | if self.return_cat: 240 | pointcloud += (self.category,) 241 | 242 | return pointcloud 243 | 244 | 245 | def __len__(self): 246 | return len(self.ptsIdx) 247 | 248 | 249 | def filename(self, idx): 250 | return self.fn[self.ptsIdx[idx]] 251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning Part Boundaries from 3D Point Clouds 2 | 3 | This repository contains code accompaning the paper: Learning Part Boundaries from 4 | 3D Point Clouds, SGP 2020. [Paper](https://arxiv.org/abs/2007.07563) 5 | 6 | ### Contents 7 | 8 | - [Training](boundary_detection/README.md) 9 | - [Evaluation](evaluation/README.md) 10 | - [Semantic Segmentation](semantic_segmentation/README.md) 11 | - [Download Datasets](datasets/README.md) 12 | - [Download Trained models](trained_models/README.md) 13 | 14 | ### Dependency 15 | - Python 2.7 16 | - Tensorflow 1.2 17 | 18 | Please contact us (Marios Loizou mloizo11@cs.ucy.ac.cy) if you have any problems about 19 | our implementation. 20 | -------------------------------------------------------------------------------- /boundary_detection/README.md: -------------------------------------------------------------------------------- 1 | ### Boundary Detection training 2 | 3 | For training either on the ABC boundary dataset or PartNet boundary dataset, using LocalEdgeConv w/normals, you can use the ```train_multi_gpu.py``` 4 | script as follows, 5 | 6 | - ABC dataset training 7 | ```bash 8 | python train_multi_gpu.py --dataset --abc_dataset --align_pointclouds --output_dir --batch_norm --num_gpu 9 | 10 | # = e.g. ../datasets/ABC_dataset/h5/boundary_seg_poisson_10K_h5 11 | # --abc_dataset: to use ABC dataset 12 | # --align_pointclouds: use spatial transformer module 13 | # --batch_norm: enable batch normalization 14 | # --num_gpu: number of GPUs used for training 15 | ``` 16 | 17 | - PartNet dataset training 18 | ```bash 19 | python train_multi_gpu.py --dataset --category --output_dir --batch_norm 20 | 21 | # = e.g. ../datasets/partnet_dataset/h5/boundary_seg_poisson_h5 22 | # = Bag 23 | # --batch_norm: enable batch normalization 24 | # --num_gpu: number of GPUs used for training 25 | # no spatial transformer was used in our PartNet experiments 26 | ``` 27 | 28 | For ```batch_size=8``` you will need 2 GPUs with 12GB memory each. 29 | -------------------------------------------------------------------------------- /datasets/README.md: -------------------------------------------------------------------------------- 1 | ### Download datasets 2 | 3 | Datasets will be released soon 4 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: Learning Part Boundaries from 3D Point Clouds 3 | description: ' ' 4 | show_downloads: true 5 | -------------------------------------------------------------------------------- /docs/images/abc_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/docs/images/abc_data.png -------------------------------------------------------------------------------- /docs/images/geometric_boundaries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/docs/images/geometric_boundaries.png -------------------------------------------------------------------------------- /docs/images/partnet_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/docs/images/partnet_data.png -------------------------------------------------------------------------------- /docs/images/semantic_segmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/docs/images/semantic_segmentation.png -------------------------------------------------------------------------------- /docs/images/teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/docs/images/teaser.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

Learning Part Boundaries from 3D Point Clouds

2 |

3 | Marios Loizou   4 | Melinos Averkiou   5 | Evangelos Kalogerakis   6 |

7 |
8 | 9 |
10 | teaser.png 11 |
12 |

13 | The output probability per point can be used in pairwise terms to improve graph-based semantic segmentation methods 14 | (left) by localizing boundaries between semantic parts. It can also be used in the geometric decomposition of point clouds 15 | into regions enclosed by sharp boundaries detected by our method (right). 16 |

17 | 18 | 19 |

Abstract

20 | We present a method that detects boundaries of parts in 3D shapes represented as point clouds. Our method is based on a 21 | graph convolutional network architecture that outputs a probability for a point to lie in an area that separates two or 22 | more parts in a 3D shape. Our boundary detector is quite generic: it can be trained to localize boundaries of semantic parts 23 | or geometric primitives commonly used in 3D modeling. Our experiments demonstrate that our method can extract more accurate 24 | boundaries that are closer to ground-truth ones compared to alternatives. We also demonstrate an application of our network 25 | to fine-grained semantic shape segmentation, where we also show improvements in terms of part labeling performance. 26 | 27 |

Boundary Datasets

28 | 29 |

Geometric segmentation dataset

30 |
31 | teaser.png 32 |
33 |

34 | Marked (with red) boundaries on ABC point clouds for training. 35 |

36 | 37 | 38 |

Semantic segmentation dataset

39 |
40 | partnet_data.png 41 |
42 |

43 | Marked boundaries on PartNet point clouds for training. 44 |

45 | 46 | __Datasets will be released soon__ 47 | 48 |

Results

49 | 50 |

Geometric part boundary detection

51 |
52 | geometric_boundaries.png 53 |
54 |

55 | Boundaries detected by our method PB-DGCNN on some example ABC point clouds. The first column 56 | on the left shows the ground truth boundaries. The second column shows boundary probabilities produced 57 | by PB-DGCNN, and the third column shows boundaries predicted by PB-DGCNN after thresholding. 58 |

59 | 60 |

Semantic shape segmentation

61 |
62 | semantic_segmentation.png 63 |
64 |

65 | Visual comparison of semantic segmentation for example PartNet point clouds, using DGCNN alone (unary), a graph-cut 66 | formulation with normal angles in the pairwise term, and a graph-cut formulation with a combination of normal angles and 67 | boundary probabilities produced by PB-DGCNN in the pairwise term. 68 |

69 | 70 | 71 | -------------------------------------------------------------------------------- /evaluation/README.md: -------------------------------------------------------------------------------- 1 | ### Evaluation 2 | 3 | For evaluating the PB-DGCNN using LocalEdgeConv w/normals on the ABC boundary dataset, you can use the ```evaluate_boundary_detection.py``` 4 | script as follows, 5 | 6 | ```bash 7 | python evaluate_boundary_detection.py --model_path --dataset 8 | 9 | # = e.g. ../trained_models/abc_local_edge_conv_with_normals/model.ckpt 10 | # = e.g. ../datasets/ABC_dataset/h5/boundary_seg_poisson_10K_evaluation_h5 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /evaluation/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /evaluation/evaluate_boundary_detection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Evaluate boundary detection w.r.t. precision, recall, f-score, boundary IoU and Chamfer Distance 3 | """ 4 | 5 | import tensorflow as tf 6 | import numpy as np 7 | import importlib 8 | import time 9 | import errno 10 | import os 11 | import sys 12 | from scipy import spatial 13 | from progressbar import ProgressBar 14 | 15 | 16 | # Import custom packages 17 | BASE_DIR = os.path.abspath(os.pardir) 18 | sys.path.append(BASE_DIR) 19 | sys.path.append(os.path.join(BASE_DIR, "models")) 20 | from ABC_data import dataset_evaluation_h5 21 | from utils import evaluation_utils 22 | 23 | 24 | def get_model(model, model_path, num_point, n_features, add_normals, align_pointclouds=True, 25 | rotate_principals_around_normal=True, gpu_index=0): 26 | batch_size = 1 27 | with tf.Graph().as_default(): 28 | with tf.device('/gpu:' + str(gpu_index)): 29 | pointclouds_ph = tf.placeholder(tf.float32, shape=(batch_size, num_point, n_features)) 30 | normals_ph = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3), name='normals_ph') 31 | is_training_ph = tf.placeholder(tf.bool, shape=()) 32 | 33 | seg_pred = model.get_model(point_cloud=pointclouds_ph, is_training=is_training_ph, normals=normals_ph, 34 | use_local_frame=True, add_normals=add_normals, bn=True, bn_decay=None, 35 | align_pointclouds=align_pointclouds, n_classes=1) 36 | 37 | seg_pred_ph = None 38 | if not rotate_principals_around_normal: 39 | seg_pred_ph = tf.placeholder(tf.float32, shape=(batch_size, num_point, 1), name='seg_pred_ph') 40 | # Concatenate features from 2 branches of the network 41 | concat_seg_pred = tf.concat([seg_pred, seg_pred_ph], axis=-1) 42 | # Max-pool 43 | max_seg_pred = tf.reduce_max(concat_seg_pred, axis=-1) 44 | probs = tf.nn.sigmoid(max_seg_pred) 45 | else: 46 | probs = tf.nn.sigmoid(seg_pred) 47 | saver = tf.train.Saver() 48 | 49 | # Create a session 50 | config = tf.ConfigProto() 51 | config.gpu_options.allow_growth = True 52 | config.allow_soft_placement = True 53 | config.gpu_options.visible_device_list = str(gpu_index) 54 | sess = tf.Session(config=config) 55 | 56 | # Restore variables from disk. 57 | saver.restore(sess, model_path) 58 | 59 | ops = {'pointclouds_ph': pointclouds_ph, 60 | 'is_training_ph': is_training_ph, 61 | 'normals_ph': normals_ph, 62 | 'seg_pred': seg_pred, 63 | 'seg_pred_ph': seg_pred_ph, 64 | 'probs': probs} 65 | 66 | return sess, ops 67 | 68 | 69 | def inference(sess, ops, pc, normals, rotate_principals_around_normal=False): 70 | is_training = False 71 | 72 | # Infer part labels 73 | feed_dict = {ops["pointclouds_ph"]: pc, 74 | ops['normals_ph']: normals, 75 | ops["is_training_ph"]: is_training} 76 | 77 | if not rotate_principals_around_normal: 78 | # Use 2-RoSy field 79 | seg_pred_1 = sess.run(ops["seg_pred"], feed_dict=feed_dict) 80 | seg_pred_1 = seg_pred_1 81 | # Invert principal directions 82 | pc[..., -6:] = -pc[..., -6:] 83 | feed_dict[ops["pointclouds_ph"]] = pc 84 | feed_dict[ops["seg_pred_ph"]] = seg_pred_1 85 | seg_probs_res, seg_pred_2 = sess.run([ops["probs"], ops["seg_pred"]], feed_dict=feed_dict) 86 | fc_output = np.concatenate((seg_pred_1, seg_pred_2), axis=-1) 87 | fc_output = np.amax(fc_output, axis=-1) 88 | else: 89 | seg_probs_res, fc_output = sess.run([ops["probs"], ops["seg_pred"]], feed_dict=feed_dict) 90 | 91 | return np.squeeze(seg_probs_res), np.squeeze(fc_output) 92 | 93 | 94 | if __name__ == '__main__': 95 | t1 = time.time() 96 | 97 | import argparse 98 | 99 | parser = argparse.ArgumentParser() 100 | parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') 101 | parser.add_argument('--model_path', default='', help='model checkpoint file path') 102 | parser.add_argument('--dataset', default='', help="Specify dataset [default: '']") 103 | parser.add_argument('--split', default='test', help='Choose which split of the dataset to use [default: test]') 104 | parser.add_argument('--num_point', type=int, default=10000, help='Point Number [default: 10000]') 105 | parser.add_argument('--conf_threshold', type=float, default=0.63, help="Confidence threshold [default: 0.63]") 106 | parser.add_argument('--radius', type=float, default=1, help='Ball radius for adaptive metrics [default: 1]') 107 | 108 | ARGS = parser.parse_args() 109 | print(vars(ARGS)) 110 | 111 | # Configuration 112 | MODEL_PATH = ARGS.model_path 113 | GPU_INDEX = ARGS.gpu 114 | MODEL = importlib.import_module("dgcnn_part_seg") # import network module 115 | DATASET_NAME = os.path.basename(ARGS.dataset.lower()) 116 | NUM_POINT = ARGS.num_point 117 | CONF_THRESHOLD = ARGS.conf_threshold 118 | RADIUS = ARGS.radius 119 | DATASET_DIR = ARGS.dataset 120 | SPLIT = ARGS.split 121 | ROTATE_PRINCIPALS_AROUND_NORMAL = True 122 | ADD_NORMALS=True 123 | ALIGN_POINTCLOUDS = True 124 | N_FEATURES = 9 125 | 126 | EVAL_FOLDER = os.path.join("evaluation_metrics", "radius_"+str(RADIUS)) 127 | try: 128 | os.makedirs(EVAL_FOLDER) 129 | except OSError as e: 130 | if e.errno != errno.EEXIST: 131 | raise 132 | 133 | # Load model 134 | sess, ops = get_model(model=MODEL, model_path=MODEL_PATH, num_point=NUM_POINT, n_features=N_FEATURES, add_normals=ADD_NORMALS, 135 | align_pointclouds=True, rotate_principals_around_normal=ROTATE_PRINCIPALS_AROUND_NORMAL, gpu_index=GPU_INDEX) 136 | 137 | # Import evaluation dataset 138 | DATASET = dataset_evaluation_h5.Dataset(pc_dataset_dir=DATASET_DIR, split=SPLIT, sampling=False, npoints=NUM_POINT, 139 | rotate_principals_around_normal= ROTATE_PRINCIPALS_AROUND_NORMAL) 140 | 141 | 142 | # Initialize evaluation metrics variables 143 | precision = 0.0; recall = 0.0; f_score = 0.0; overall_dcd = 0; exclude_models = 0; boundary_iou = 0.0 144 | 145 | # For noise addition 146 | rng1 = np.random.RandomState(2) 147 | rng2 = np.random.RandomState(3) 148 | rng3 = np.random.RandomState(4) 149 | 150 | bar = ProgressBar() 151 | for i in bar(range(len(DATASET))): 152 | 153 | # Get pointclouds from dataset 154 | points, input_features, pointcloud_features, boundary_points, boundary_normals = \ 155 | evaluation_utils.get_pointcloud_evaluation(dataset=DATASET, idx=i, n_features=N_FEATURES) 156 | print("Infer boundaries on point cloud ({current:d}/{total:d}) with {n_points:d} points, from {dataset:s}, " 157 | " using model {model:s}...".format(current=i + 1, dataset=os.path.basename(DATASET_NAME),n_points=len(points), 158 | total=len(DATASET), model=MODEL_PATH)) 159 | 160 | # Add noise 161 | # Jitter points 162 | points = evaluation_utils.jitter_points(points=points, sigma=0.005, clip=0.01, rng=rng1) 163 | input_features[:, 0:3] = np.copy(points) 164 | # Jitter normals 165 | pointcloud_features[:, 0:3], R = evaluation_utils.jitter_direction_vectors(pointcloud_features[:, 0:3], 166 | sigma=1.5, clip=3, rng1=rng2, 167 | rng2=rng3, return_noise=True) 168 | # Jitter tangent vectors 169 | input_features[:, 3:6] = np.squeeze(np.matmul(R, input_features[:, 3:6, np.newaxis])) 170 | input_features[:, 6:9] = np.squeeze(np.matmul(R, input_features[:, 6:9, np.newaxis])) 171 | 172 | # Boundary inference 173 | normals = np.expand_dims(pointcloud_features[:, 0:3], axis=0) 174 | probs, fc_output = inference(sess=sess, ops=ops, pc=np.expand_dims(input_features, 0), normals=normals, 175 | rotate_principals_around_normal=ROTATE_PRINCIPALS_AROUND_NORMAL) 176 | 177 | # Threshold boundary confidence 178 | segp = np.zeros((NUM_POINT,1), dtype=np.float32) 179 | segp[probs > CONF_THRESHOLD] = 1 180 | 181 | # Evaluate boundary detection performance 182 | # Create kd-tree for surface points 183 | points_KDTree = spatial.cKDTree(points, copy_data=False, balanced_tree=False, compact_nodes=False) 184 | # Find maximum sampling distance 185 | nn_dist, _ = points_KDTree.query(points, k=2) 186 | nn_dist = nn_dist[:, 1] 187 | max_dist = np.amax(nn_dist) 188 | # Boundary tolerance threshold 189 | dist = RADIUS * max_dist 190 | 191 | # Calculate precision 192 | boundary_points_pred = points[np.squeeze(segp==1)] 193 | shape_precision, TP_precision, FP_precision = evaluation_utils.precision(boundary_points_gt=boundary_points, 194 | boundary_points_pred=boundary_points_pred, 195 | dist=dist) 196 | precision += shape_precision 197 | 198 | # Calculate recall 199 | shape_recall, TP_recall, FP_recall = evaluation_utils.recall(boundary_points_gt=boundary_points, 200 | boundary_points_pred=boundary_points_pred, 201 | dist=dist) 202 | recall += shape_recall 203 | 204 | # Calculate bIoU 205 | try: 206 | boundary_iou += ((TP_precision + TP_recall) / float(len(boundary_points) + len(boundary_points_pred))) 207 | except: 208 | boundary_iou += 0.0 209 | 210 | 211 | if len(boundary_points) == 0: 212 | # Ground truth segmentation -> exclude from evaluation 213 | exclude_models += 1 214 | else: 215 | seg_2_boundaries = np.unique(segp) 216 | if len(seg_2_boundaries) == 1: 217 | # Predicted segmentation -> penalize for chamfer distance 218 | overall_dcd += evaluation_utils.pc_bounding_box_diagonal(points) 219 | else: 220 | # Evaluate inferred boundaries wrt. chamfer distance 221 | dcd = evaluation_utils.chamfer_distance(segmentation1_points=boundary_points, 222 | segmentation2_points=points[np.squeeze(segp==1)], 223 | points=points) 224 | overall_dcd += dcd 225 | 226 | # Log network evaluatioo 227 | total_seen = len(DATASET) 228 | # Log precision 229 | precision /= float(total_seen - exclude_models) 230 | buf = 'Precision: %f\n' % (precision) 231 | # Log recall 232 | recall /= float(total_seen) 233 | buf += 'Recall: %f\n' % (recall - exclude_models) 234 | # Log f-score 235 | try: 236 | f_score = 2.0 * (precision * recall) / (precision + recall) 237 | except ZeroDivisionError: 238 | f_score = 0.0 239 | buf += 'F-score: %f\n' % (f_score) 240 | # Log chamfer distance 241 | try: 242 | mean_dcd = overall_dcd / float(total_seen - exclude_models) 243 | except ZeroDivisionError: 244 | mean_dcd = 1e5 245 | buf += 'Chamfer distance: %f\n' % (mean_dcd) 246 | boundary_iou /= float(total_seen - exclude_models) 247 | buf += 'Boundary IoU: %f\n' % (boundary_iou) 248 | buf += '%f,%f,%f,%f,%f\n' % (precision, recall, f_score, mean_dcd, boundary_iou) 249 | buf += '=SPLIT("%3.1f,%3.1f,%3.1f,%3.1f,%3.1f", ",")\n' % (precision*100, recall*100, f_score*100, mean_dcd*100, boundary_iou*100) 250 | print(buf) 251 | with open(os.path.join(EVAL_FOLDER, "evaluation_metrics.txt"), 'w') as fout: 252 | fout.write(buf) 253 | 254 | elapsed_time = time.time() - t1 255 | print("\nFinish evaluation -- time passed {hours:d}:{minutes:d}:{seconds:d}" 256 | .format(hours=int((elapsed_time / 60 ** 2) % (60 ** 2)), minutes=int((elapsed_time / 60) % (60)), 257 | seconds=int(elapsed_time % 60))) 258 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /models/dgcnn_part_seg.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import tensorflow as tf 4 | import numpy as np 5 | 6 | # Import custom packages 7 | BASE_DIR = os.path.abspath(os.pardir) 8 | sys.path.append(BASE_DIR) 9 | from utils import tf_util 10 | from transform_nets import global_spatial_transformer 11 | 12 | 13 | def get_model(point_cloud, is_training, normals, use_local_frame=True, add_normals=False, bn=True, bn_decay=None, 14 | use_xavier=True, align_pointclouds=False, drop_prob=0.5, n_classes=1, k=20): 15 | """ Part segmentation DGCNN, input is BxNxnFeatures, output BxnClasses """ 16 | 17 | batch_size = point_cloud.get_shape()[0].value 18 | num_point = point_cloud.get_shape()[1].value 19 | 20 | # Input xyz coordinates 21 | input_xyz = tf.slice(point_cloud, [0,0,0], [-1,-1,3]) 22 | 23 | # Add tangent vectors and normals for local frames calculation 24 | local_frame_data = None 25 | if use_local_frame: 26 | tangent_vec1 = tf.slice(point_cloud, [0, 0, 3], [-1, -1, 3], name="tangent_vec1") 27 | tangent_vec2 = tf.slice(point_cloud, [0, 0, 6], [-1, -1, 3], name="tangent_vec2") 28 | local_frame_data = (tangent_vec1, tangent_vec2, normals) 29 | 30 | # Point clouds global alignment 31 | if align_pointclouds: 32 | # Calculate pairwise distance on global coordinates and find k-nn's for each point 33 | adj = tf_util.pairwise_distance(input_xyz) 34 | nn_idx = tf_util.knn(adj, k=k) 35 | input_xyz = tf.expand_dims(input_xyz, -1) 36 | edge_feature = tf_util.get_edge_feature(input_xyz, nn_idx=nn_idx, k=k) 37 | 38 | with tf.variable_scope('transform_net_global') as sc: 39 | global_transform = global_spatial_transformer(point_cloud=edge_feature, is_training=is_training, bn=bn, 40 | bn_decay=bn_decay, is_dist=True) 41 | input_xyz = tf.matmul(tf.squeeze(input_xyz, axis=-1), global_transform) 42 | 43 | if add_normals: 44 | if input_xyz.shape.ndims == 4: 45 | input_xyz = tf.squeeze(input_xyz, axis=-1) 46 | input_xyz = tf.concat([input_xyz, normals], axis=-1) 47 | 48 | input_image = tf.expand_dims(input_xyz, -1) 49 | adj = tf_util.pairwise_distance(input_xyz) 50 | nn_idx = tf_util.knn(adj, k=k) 51 | edge_feature = tf_util.get_edge_feature(input_image, nn_idx=nn_idx, k=k, use_local_frame=use_local_frame, 52 | local_frame_data=local_frame_data, add_normals=add_normals) 53 | 54 | # EdgeConv layer 1 {64, 64} 55 | out1 = tf_util.conv2d(edge_feature, 64, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 56 | scope='adj_conv1', bn_decay=bn_decay, is_dist=True, use_xavier=use_xavier) 57 | 58 | out2 = tf_util.conv2d(out1, 64, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 59 | scope='adj_conv2', bn_decay=bn_decay, is_dist=True, use_xavier=use_xavier) 60 | 61 | net_1 = tf.reduce_max(out2, axis=-2, keep_dims=True) 62 | 63 | # EdgeConv layer 2 {64, 64} 64 | adj = tf_util.pairwise_distance(net_1) 65 | nn_idx = tf_util.knn(adj, k=k) 66 | edge_feature = tf_util.get_edge_feature(net_1, nn_idx=nn_idx, k=k) 67 | 68 | out3 = tf_util.conv2d(edge_feature, 64, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 69 | scope='adj_conv3', bn_decay=bn_decay, is_dist=True, use_xavier=use_xavier) 70 | 71 | out4 = tf_util.conv2d(out3, 64, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 72 | scope='adj_conv4', bn_decay=bn_decay, is_dist=True, use_xavier=use_xavier) 73 | 74 | net_2 = tf.reduce_max(out4, axis=-2, keep_dims=True) 75 | 76 | # EdgeConv layer 3 {64} 77 | adj = tf_util.pairwise_distance(net_2) 78 | nn_idx = tf_util.knn(adj, k=k) 79 | edge_feature = tf_util.get_edge_feature(net_2, nn_idx=nn_idx, k=k) 80 | 81 | out5 = tf_util.conv2d(edge_feature, 64, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 82 | scope='adj_conv5', bn_decay=bn_decay, is_dist=True, use_xavier=use_xavier) 83 | 84 | net_3 = tf.reduce_max(out5, axis=-2, keep_dims=True) 85 | 86 | # [EdgeConv1, EdgeConv2, EdgeConv3] -> MLP {64} 87 | out7 = tf_util.conv2d(tf.concat([net_1, net_2, net_3], axis=-1), 1024, [1, 1], padding='VALID', stride=[1, 1], 88 | bn=bn, is_training=is_training, scope='adj_conv7', bn_decay=bn_decay, is_dist=True, 89 | use_xavier=use_xavier) 90 | 91 | out_max = tf_util.max_pool2d(out7, [num_point, 1], padding='VALID', scope='maxpool') 92 | expand = tf.tile(out_max, [1, num_point, 1, 1]) 93 | 94 | # Concat [global_feature, EdgeConv1, EdgeConv2, EdgeConv3] 95 | concat = tf.concat(axis=3, values=[expand, net_1, net_2, net_3]) 96 | 97 | # FC layer - MLP{256, 256, 128, n_classes} 98 | net2 = tf_util.conv2d(concat, 256, [1, 1], padding='VALID', stride=[1, 1], bn_decay=bn_decay, bn=bn, 99 | is_training=is_training, scope='seg/conv1', is_dist=True, use_xavier=use_xavier) 100 | net2 = tf_util.dropout(net2, keep_prob=1-drop_prob, is_training=is_training, scope='seg/dp1') 101 | net2 = tf_util.conv2d(net2, 256, [1, 1], padding='VALID', stride=[1, 1], bn_decay=bn_decay, bn=bn, 102 | is_training=is_training, scope='seg/conv2', is_dist=True, use_xavier=use_xavier) 103 | net2 = tf_util.dropout(net2, keep_prob=1-drop_prob, is_training=is_training, scope='seg/dp2') 104 | net2 = tf_util.conv2d(net2, 128, [1, 1], padding='VALID', stride=[1, 1], bn_decay=bn_decay, bn=bn, 105 | is_training=is_training, scope='seg/conv3', is_dist=True, use_xavier=use_xavier) 106 | net2 = tf_util.conv2d(net2, n_classes, [1, 1], padding='VALID', stride=[1, 1], activation_fn=None, bn=False, 107 | scope='seg/conv4', is_dist=False, use_xavier=use_xavier) 108 | 109 | net2 = tf.reshape(net2, [batch_size, num_point, n_classes]) 110 | 111 | return net2 112 | 113 | 114 | def get_loss(**kwargs): 115 | mandatory_args = ["seg_pred", "seg"] 116 | for arg in mandatory_args: 117 | if arg not in kwargs.keys(): 118 | print("ERROR: {arg:s} not specified for calling get_loss()" .format(arg=arg)) 119 | exit(-1) 120 | 121 | if "weights" in kwargs.keys(): 122 | # Call sigmoid + weighted cross-entropy 123 | return _get_loss_with_weights(seg_pred=kwargs["seg_pred"], seg=kwargs["seg"], weights=kwargs["weights"]) 124 | 125 | # Call softmax + cross-entropy 126 | return _get_loss(seg_pred=kwargs["seg_pred"], seg=kwargs["seg"]) 127 | 128 | 129 | def _get_loss(seg_pred, seg): 130 | per_instance_seg_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=seg_pred, labels=seg), 131 | axis=1) 132 | seg_loss = tf.reduce_mean(per_instance_seg_loss) 133 | per_instance_seg_pred_res = tf.argmax(seg_pred, 2) 134 | 135 | return seg_loss, per_instance_seg_loss, per_instance_seg_pred_res 136 | 137 | 138 | def _get_loss_with_weights(seg_pred, seg, weights): 139 | per_instance_seg_loss = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(targets=tf.squeeze(seg), 140 | logits=tf.squeeze(seg_pred), 141 | pos_weight=weights), axis=1) 142 | seg_loss = tf.reduce_mean(per_instance_seg_loss) 143 | 144 | return seg_loss, per_instance_seg_loss -------------------------------------------------------------------------------- /models/transform_nets.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import tensorflow as tf 4 | import numpy as np 5 | 6 | # Import custom packages 7 | BASE_DIR = os.path.abspath(os.pardir) 8 | sys.path.append(BASE_DIR) 9 | from utils import tf_util 10 | 11 | 12 | def global_spatial_transformer(point_cloud, is_training, K=3, bn=True, bn_decay=None, is_dist=True): 13 | """ Input (XYZ) Transform Net, input is BxNx3 gray image 14 | Return: 15 | Transformation matrix of size KxK """ 16 | 17 | batch_size = point_cloud.get_shape()[0].value 18 | num_point = point_cloud.get_shape()[1].value 19 | 20 | net = tf_util.conv2d(point_cloud, 64, [1, 1], padding='VALID', stride=[1,1], bn=bn, is_training=is_training, 21 | scope='tconv1', bn_decay=bn_decay, is_dist=is_dist) 22 | net = tf_util.conv2d(net, 128, [1,1], padding='VALID', stride=[1,1], bn=bn, is_training=is_training, 23 | scope='tconv2', bn_decay=bn_decay, is_dist=is_dist) 24 | net = tf.reduce_max(net, axis=-2, keep_dims=True) 25 | 26 | net = tf_util.conv2d(net, 1024, [1,1], padding='VALID', stride=[1,1], bn=bn, is_training=is_training, 27 | scope='tconv3', bn_decay=bn_decay, is_dist=is_dist) 28 | net = tf_util.max_pool2d(net, [num_point,1], padding='VALID', scope='tmaxpool') 29 | 30 | net = tf.reshape(net, [batch_size, -1]) 31 | net = tf_util.fully_connected(net, 512, bn=bn, is_training=is_training, scope='tfc1', bn_decay=bn_decay, 32 | is_dist=is_dist) 33 | net = tf_util.fully_connected(net, 256, bn=bn, is_training=is_training, scope='tfc2', bn_decay=bn_decay, 34 | is_dist=is_dist) 35 | 36 | with tf.variable_scope('transform_XYZ') as sc: 37 | weights = tf.get_variable('weights', [256, K*K], 38 | initializer=tf.constant_initializer(0.0), 39 | dtype=tf.float32) 40 | biases = tf.get_variable('biases', [K*K], 41 | initializer=tf.constant_initializer(0.0), 42 | dtype=tf.float32) 43 | biases += tf.constant(np.eye(K).flatten(), dtype=tf.float32) 44 | transform = tf.matmul(net, weights) 45 | transform = tf.nn.bias_add(transform, biases) 46 | 47 | transform = tf.reshape(transform, [batch_size, K, K]) 48 | 49 | return transform 50 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex.cpp: -------------------------------------------------------------------------------- 1 | /* GCMex.cpp Version 2.3.0 2 | * 3 | * Copyright 2009 Brian Fulkerson 4 | */ 5 | 6 | #include "mex.h" 7 | #include "GCoptimization.h" 8 | #include 9 | 10 | void mexFunction( 11 | int nout, /* number of expected outputs */ 12 | mxArray *out[], /* mxArray output pointer array */ 13 | int nin, /* number of inputs */ 14 | const mxArray *in[] /* mxArray input pointer array */ 15 | ) 16 | { 17 | 18 | enum {IN_CLASS=0,IN_UNARY,IN_PAIRWISE,IN_LABELCOST,IN_EXPANSION} ; 19 | enum {OUT_LABELS=0, OUT_ENERGY, OUT_ENERGYAFTER} ; 20 | 21 | bool expansion = false; 22 | 23 | /**************************************************************************** 24 | * ERROR CHECKING 25 | ***************************************************************************/ 26 | if (nin != 4 && nin != 5) 27 | mexErrMsgTxt("Four or five arguments are required."); 28 | if (nin == 5) 29 | expansion = *mxGetPr(in[IN_EXPANSION]) > 0; 30 | if (nout > 3) 31 | mexErrMsgTxt("At most three outputs are allowed."); 32 | 33 | if(mxGetClassID(in[IN_CLASS]) != mxDOUBLE_CLASS) 34 | mexErrMsgTxt("Class must be a vector of class DOUBLE"); 35 | if(mxGetM(in[IN_CLASS]) != 1 && mxGetN(in[IN_CLASS]) != 1) 36 | mexErrMsgTxt("Class must be a vector"); 37 | 38 | if(mxGetNumberOfDimensions(in[IN_UNARY]) != 2 || 39 | mxGetClassID(in[IN_UNARY]) != mxSINGLE_CLASS) 40 | mexErrMsgTxt("Unary term must be a matrix of class SINGLE"); 41 | 42 | if(mxGetClassID(in[IN_LABELCOST]) != mxSINGLE_CLASS) 43 | mexErrMsgTxt("Labelcost term must be a matrix of class SINGLE"); 44 | 45 | int num_labels = mxGetM(in[IN_UNARY]); 46 | int num_pixels = mxGetN(in[IN_UNARY]); 47 | 48 | if(mxGetM(in[IN_CLASS]) != num_pixels && mxGetN(in[IN_CLASS]) != num_pixels) 49 | mexErrMsgTxt("Class size does not match cols in Unary term."); 50 | if(mxGetM(in[IN_LABELCOST]) != mxGetN(in[IN_LABELCOST]) || 51 | mxGetM(in[IN_LABELCOST]) != num_labels) 52 | mexErrMsgTxt("Labelcost is not symmetric or does not match rows in Unary term."); 53 | 54 | if(mxGetM(in[IN_PAIRWISE]) != num_pixels || 55 | mxGetN(in[IN_PAIRWISE]) != num_pixels) 56 | mexErrMsgTxt("Pairwise is not symmetric or does not match cols in Unary term."); 57 | 58 | 59 | /* Create output arrays */ 60 | mwSize dims[2] = {1,0}; 61 | out[OUT_ENERGY] = mxCreateNumericArray(1, dims, mxDOUBLE_CLASS, mxREAL); 62 | out[OUT_ENERGYAFTER] = mxCreateNumericArray(1, dims, mxDOUBLE_CLASS, mxREAL); 63 | double * energy = mxGetPr(out[OUT_ENERGY]); 64 | double * energy_after = mxGetPr(out[OUT_ENERGYAFTER]); 65 | 66 | mwSize pdims[2] = {num_pixels,1}; 67 | out[OUT_LABELS] = mxCreateNumericArray(1,pdims,mxDOUBLE_CLASS, mxREAL); 68 | double * labels = mxGetPr(out[OUT_LABELS]); 69 | 70 | /* Data costs are nlabels rows x npixels cols */ 71 | float * data = (float *)mxGetData(in[IN_UNARY]); 72 | double * classes = mxGetPr(in[IN_CLASS]); 73 | 74 | if (num_pixels == 1) { /* one pixel is a special case */ 75 | *energy = data[(int)classes[0]]; 76 | int minlabel = (int)classes[0]; 77 | double mincost = *energy; 78 | for(int i = 0; i < num_labels; i++) 79 | if(data[i] < mincost) { 80 | mincost = data[i]; 81 | minlabel = i; 82 | } 83 | labels[0] = minlabel; 84 | *energy_after = mincost; 85 | return; 86 | } 87 | 88 | /**************************************************************************** 89 | * Setup Graph and Perform Optimization 90 | ***************************************************************************/ 91 | try { 92 | GCoptimizationGeneralGraph * gc = new GCoptimizationGeneralGraph(num_pixels, num_labels); 93 | 94 | for (int i = 0; i < num_pixels; i++) { 95 | gc->setLabel(i, (int)classes[i]); 96 | } 97 | 98 | gc->setDataCost(data); 99 | 100 | /* Data costs are nlabels rows x npixels cols */ 101 | float * labelcost = (float *)mxGetData(in[IN_LABELCOST]); 102 | gc->setSmoothCost(labelcost); 103 | 104 | 105 | /* Set spatialy varying part of the smoothness cost with the neighborhood 106 | */ 107 | mwSize total = 0; 108 | double * pair = mxGetPr(in[IN_PAIRWISE]); 109 | mwIndex * ir = mxGetIr(in[IN_PAIRWISE]); 110 | mwIndex * jc = mxGetJc(in[IN_PAIRWISE]); 111 | for (int col=0; col < num_pixels; col++) { 112 | mwIndex starting_row_index = jc[col]; 113 | mwIndex stopping_row_index = jc[col+1]; 114 | if (starting_row_index == stopping_row_index) 115 | continue; 116 | 117 | for (int idx = starting_row_index; idx < stopping_row_index; idx++) { 118 | /* only set bottom triangle of pairwise, per GC_README */ 119 | if ( ir[idx] > col ) 120 | gc->setNeighbors(ir[idx], col, pair[total]); 121 | total++; 122 | } 123 | } 124 | 125 | *energy = gc->compute_energy(); 126 | 127 | /* From GC_README 128 | * The expansion algorithm for energy minimization can be used whenever for 129 | * any 3 labels a,b,c V(a,a) + V(b,c) <= V(a,c)+V(b,a). In other words, 130 | * expansion algorithm can be used if the binary energy for the expansion 131 | * algorithm step is regular, using V. Kolmogorov's terminology. 132 | * 133 | * The swap algorithm for energy minimization can be used whenever for any 2 134 | * labels a,b V(a,a) + V(b,b) <= V(a,b)+V(b,a). In other words, swap 135 | * algorithm can be used if the binary energy for the swap algorithm step is 136 | * regular, using V. Kolmogorov's terminology. 137 | */ 138 | 139 | if(expansion) 140 | gc->expansion(); 141 | else 142 | gc->swap(); 143 | 144 | *energy_after = gc->compute_energy(); 145 | 146 | for (int i = 0; i < num_pixels; i++ ) 147 | labels[i] = gc->whatLabel(i); 148 | 149 | delete gc; 150 | } 151 | catch (GCException e) { 152 | mexErrMsgTxt(e.message); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex.m: -------------------------------------------------------------------------------- 1 | % GCMEX An efficient graph-cut based energy minimization 2 | % 3 | % [LABELS ENERGY ENERGYAFTER] = 4 | % GCMEX(CLASS, UNARY, PAIRWISE, LABELCOST,EXPANSION) 5 | % 6 | % Runs a minimization starting with the labels for each node defined 7 | % by CLASS, with unary potentials UNARY and the structure of the 8 | % graph and pairwise potentials defined by PAIRWISE. LABELCOST 9 | % determines data costs in terms of the labels of adjacent nodes. 10 | % 11 | % Parameters: 12 | % CLASS:: A 1xN vector which specifies the initial labels of each 13 | % of the N nodes in the graph 14 | % UNARY:: A CxN matrix specifying the potentials (data term) for 15 | % each of the C possible classes at each of the N nodes. 16 | % PAIRWISE:: An NxN sparse matrix specifying the graph structure and 17 | % cost for each link between nodes in the graph. 18 | % LABELCOST:: A CxC matrix specifying the fixed label cost for the 19 | % labels of each adjacent node in the graph. 20 | % EXPANSION:: A 0-1 flag which determines if the swap or expansion 21 | % method is used to solve the minimization. 0 == swap, 22 | % 1 == expansion. If ommitted, defaults to swap. 23 | % 24 | % Outputs: 25 | % LABELS:: A 1xN vector of the final labels. 26 | % ENERGY:: The energy of the initial labeling contained in CLASS 27 | % ENERGYAFTER:: The energy of the final labels LABELS 28 | % 29 | % How do I know if I should use swap or expansion? From GC_README.txt: 30 | % The expansion algorithm for energy minimization can be used 31 | % whenever for any 3 labels a,b,c V(a,a) + V(b,c) <= V(a,c)+V(b,a). 32 | % In other words, expansion algorithm can be used if the binary 33 | % energy for the expansion algorithm step is regular, using V. 34 | % Kolmogorov's terminology. 35 | % 36 | % The swap algorithm for energy minimization can be used whenever 37 | % for any 2 labels a,b V(a,a) + V(b,b) <= V(a,b)+V(b,a). In other 38 | % words, swap algorithm can be used if the binary energy for the 39 | % swap algorithm step is regular, using V. Kolmogorov's terminology. 40 | % 41 | % GCMex Version 2.3.0 42 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex.mexa64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/semantic_segmentation/GCMex/GCMex.mexa64 -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex.mexw64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/semantic_segmentation/GCMex/GCMex.mexw64 -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex.mexw64.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marios2019/learning_part_boundaries/43ab4b6d23daf9af9db9d366bd332d89f8da179b/semantic_segmentation/GCMex/GCMex.mexw64.pdb -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex_compile.m: -------------------------------------------------------------------------------- 1 | if strcmp(computer(),'GLNXA64') || ... 2 | strcmp(computer(),'PCWIN64') || ... 3 | strcmp(computer(),'MACI64') 4 | mex -O -g -largeArrayDims GCMex.cpp graph.cpp GCoptimization.cpp LinkedBlockList.cpp maxflow.cpp 5 | else 6 | mex -O -g GCMex.cpp graph.cpp GCoptimization.cpp LinkedBlockList.cpp maxflow.cpp 7 | end 8 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GCMex_test.m: -------------------------------------------------------------------------------- 1 | 2 | W = 10; 3 | H = 5; 4 | segclass = zeros(50,1); 5 | pairwise = sparse(50,50); 6 | unary = zeros(7,25); 7 | [X Y] = meshgrid(1:7, 1:7); 8 | labelcost = min(4, (X - Y).*(X - Y)); 9 | 10 | for row = 0:H-1 11 | for col = 0:W-1 12 | pixel = 1+ row*W + col; 13 | if row+1 < H, pairwise(pixel, 1+col+(row+1)*W) = 1; end 14 | if row-1 >= 0, pairwise(pixel, 1+col+(row-1)*W) = 1; end 15 | if col+1 < W, pairwise(pixel, 1+(col+1)+row*W) = 1; end 16 | if col-1 >= 0, pairwise(pixel, 1+(col-1)+row*W) = 1; end 17 | if pixel < 25 18 | unary(:,pixel) = [0 10 10 10 10 10 10]'; 19 | else 20 | unary(:,pixel) = [10 10 10 10 0 10 10]'; 21 | end 22 | end 23 | end 24 | 25 | [labels E Eafter] = GCMex(segclass, single(unary), pairwise, single(labelcost),0); 26 | 27 | fprintf('E: %d (should be 260), Eafter: %d (should be 44)\n', E, Eafter); 28 | fprintf('unique(labels) should be [0 4] and is: ['); 29 | fprintf('%d ', unique(labels)); 30 | fprintf(']\n'); 31 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/GC_README.txt: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # # 3 | # GC_optimization - software for energy minimization with graph cuts # 4 | # Version 2.3 # 5 | # http://www.csd.uwo.ca/faculty/olga/software.html # 6 | # # 7 | # Copyright 2007 Olga Veksler (olga@csd.uwo.ca) # 8 | # # 9 | # I would like to thank Andrew Delong for his invaluable help # 10 | # with C++ when redesigning the GC interface. Thank you for # 11 | # helping me to make my code 5 times shorter and 100 times more # 12 | # debuggable. And not for scaring me with DLL's :) # 13 | ######################################################################### 14 | 15 | /* email olga@csd.uwo.ca for any questions, suggestions and bug reports 16 | 17 | /***************** IMPORTANT!!!!!!!!!!!*********************************************************** 18 | 19 | If you use this software, YOU HAVE TO REFERENCE (at least) 3 papers, the citations [1] 20 | [2] and [3] below 21 | 22 | /****************************************************************************************************/ 23 | /* 24 | 25 | 26 | 1. Introduction. 27 | 28 | 29 | 30 | This software library implements the Graph Cuts Energy Minimization methods 31 | described in 32 | 33 | 34 | [1] Efficient Approximate Energy Minimization via Graph Cuts 35 | Yuri Boykov, Olga Veksler, Ramin Zabih, 36 | IEEE transactions on PAMI, vol. 20, no. 12, p. 1222-1239, November 2001. 37 | 38 | 39 | 40 | This software can be used only for research purposes, you should cite 41 | the aforementioned paper in any resulting publication. 42 | If you wish to use this software (or the algorithms described in the aforementioned paper) 43 | for commercial purposes, you should be aware that there is a US patent: 44 | 45 | R. Zabih, Y. Boykov, O. Veksler, 46 | "System and method for fast approximate energy minimization via graph cuts ", 47 | United Stated Patent 6,744,923, June 1, 2004 48 | 49 | 50 | 51 | /* Together with the library implemented by O. Veksler, we provide, with the permission of the 52 | V. Kolmogorov and Y. Boykov the following libraries: 53 | 54 | 55 | 1) energy.h, which was developed by Vladimir Kolmogorov and implements binary energy minimization 56 | technique described in 57 | 58 | [2] What Energy Functions can be Minimized via Graph Cuts? 59 | Vladimir Kolmogorov and Ramin Zabih. 60 | To appear in IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI). 61 | Earlier version appeared in European Conference on Computer Vision (ECCV), May 2002. 62 | 63 | 64 | We use "energy.h" to implement the binary energy minimization step 65 | for the alpha-expansion and swap algorithms. The graph construction provided by "energy.h" is 66 | more efficient (and slightly more general) than the original graph construction for the 67 | alpha-expansion algorithm in the paper cited as [1] 68 | 69 | 70 | This software can be used only for research purposes. IF YOU USE THIS SOFTWARE, 71 | YOU SHOULD CITE THE AFOREMENTIONED PAPER [2] IN ANY RESULTING PUBLICATION. 72 | 73 | 74 | 75 | 2) graph.h, block.h, maxflow.cpp 76 | 77 | This software library implements the maxflow algorithm 78 | described in 79 | 80 | [3] An Experimental Comparison of Min-Cut/Max-Flow Algorithms 81 | for Energy Minimization in Vision. 82 | Yuri Boykov and Vladimir Kolmogorov. 83 | In IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), 84 | September 2004 85 | 86 | This algorithm was developed by Yuri Boykov and Vladimir Kolmogorov 87 | at Siemens Corporate Research. To make it available for public use, 88 | it was later reimplemented by Vladimir Kolmogorov based on open publications. 89 | 90 | If you use this software for research purposes, you should cite 91 | the aforementioned paper in any resulting publication. 92 | */ 93 | 94 | /* These 4 files (energy.h,graph.h, block.h, maxflow.cpp) are included in the curent library with permission of 95 | Vladimir Kolmogorov and Yuri Boykov. 96 | The can also be downloaded independently from Vladimir Kolmogorov's 97 | website: http://www.adastral.ucl.ac.uk/~vladkolm/software.html 98 | 99 | 100 | Tested under windows, Visual C++ 7.1 compiler 101 | 102 | ################################################################## 103 | 104 | 2. License. 105 | 106 | This program is free software; you can redistribute it and/or modify 107 | it under the terms of the GNU General Public License as published by 108 | the Free Software Foundation; either version 2 of the License, or 109 | (at your option) any later version. 110 | 111 | This program is distributed in the hope that it will be useful, 112 | but WITHOUT ANY WARRANTY; without even the implied warranty of 113 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 114 | GNU General Public License for more details. 115 | 116 | You should have received a copy of the GNU General Public License 117 | along with this program; if not, write to the Free Software 118 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 119 | 120 | 121 | 122 | ################################################################## 123 | 124 | 3. Energy Minimization 125 | 126 | This software is for minimizing energy functions of the form: 127 | 128 | E(l) = sum_p D(p,l_p) + sum_{p,q} Vpq(l_p,l_q) 129 | 130 | Here we have a finite set of sites (or pixels) P and a finite set of labels L. 131 | A labeling l is assignments of labels in L to pixels in P. The individual pixels 132 | are referred to with small letters p and q, label of pixel p is denoted by l_p, 133 | and the set of all label-pixel assignments is denoted by l, that is 134 | l = {l_p | p in P}. 135 | 136 | The first term in the energy function E(l) is typically called the data term, and 137 | it consists of the sum over all pixels p of the penalty(or cost) D(p,l_p), what 138 | should be the cost of assigning label l_p to pixel p. D(p,l_p) can be arbitrary. 139 | 140 | The second term is a sum over all pairs of neighboring pixels {p,q}. 141 | That is there is a neighborhood relation on the set of pixels (this relationship 142 | is symmetric, that is if p is a neighbor of q then q is a neighbor of p). 143 | Here we assume that the neighbor pairs are unordered. This means that if pixels p and q are 144 | neighbors, then there is either Vpq(l_p,l_q) in the second sum of the energy, 145 | or Vqp(l_q,l_p), but not both. This is not a restriction, since in practice, one can always go 146 | from the ordered energy to the unordered one. This second term is typically called the smoothness 147 | term. 148 | 149 | The expansion algorithm for energy minimization can be used whenever for any 3 labels a,b,c 150 | V(a,a) + V(b,c) <= V(a,c)+V(b,a). In other words, expansion algorithm can be used if 151 | the binary energy for the expansion algorithm step is regular, using V. Kolmogorov's terminology. 152 | 153 | The swap algorithm for energy minimization can be used whenever for any 2 labels a,b 154 | V(a,a) + V(b,b) <= V(a,b)+V(b,a). In other words, swap algorithm can be used if 155 | the binary energy for the swap algorithm step is regular, using V. Kolmogorov's terminology. 156 | 157 | ################################################################## 158 | 159 | 4. Datatypes 160 | 161 | a) EnergyTermType. This is the type for each individual energy term, 162 | that is for the terms D and V. which By default, it is set to int. 163 | To change it, go into file "graph.h" and modify the statement 164 | 165 | "typedef int captype;" from short to any desired type. 166 | 167 | 168 | b) EnergyType. This is the type for the total energy (for the sum of all 169 | the D and V terms). By default, it is set to int. To change it, go into file "graph.h" 170 | and change the statement "typedef int flowtype;" to any desired type. Be very careful to 171 | avoid overflow if you change this type. 172 | The sum of many terms of type EnergyTermType shouldn't overflow the EnergyType. 173 | 174 | 175 | c) LabelID, is the type to use for the label names. Currently set to integers. In order 176 | to save space, you may want to change it to char or short. Can be also set to long, 177 | but not to float or double. To change this type, go to "GCoptimization.h" 178 | 179 | typedef int LabelID; 180 | 181 | d) SiteID: the type to use for site (pixel) names. Currently set to integers. For 182 | smaller problems, to save space, you may want to change it to short. Can be also set to long, 183 | but not to float or double. To change this type, go to "GCoptimization.h" 184 | 185 | /* Type for site (pixe). Can be set to short, int, long */ 186 | typedef int SiteID 187 | 188 | 189 | 190 | ########################################################################### 191 | 192 | 193 | 5. Specifying the energy 194 | 195 | Before optimizing the energy, one has to specify it, that is specify the number of 196 | labels, number of pixels, neighborhood system, the data terms, and the smoothness terms. 197 | There are 2 constructors to use, one in case of the grid graph, and another in case 198 | of a general graph. 199 | In all cases, it is assumed that the sites go between 0...num_sites-1, 200 | and labels go between 0....num_labels-1. 201 | For a grid (4-connected) graph, site at coordinates (x,y) is numbered with x+y*width, where width 202 | is the width of the grid, that is the row-major ordering of arrays is assumed. 203 | ________________________________________________________________________________________________ 204 | 205 | Constructor A. 206 | 207 | GCoptimizationGridGraph(int width, int height,int num_labels); 208 | 209 | Use this constructor only for grid of size width by height. If you use this constructor, 210 | 4 connected grid neigbhorhood structure is assumed, so you don't need to specify neighborhood 211 | structure separately (and indeed cannot do so). 212 | _______________________________________________________________________________________________ 213 | 214 | Constructor B. 215 | 216 | GCoptimizationGeneralGraph(int num_sites,int num_labels); 217 | 218 | 219 | Use this constructor for general graphs. If you use this constructor, you must setup up 220 | neighborhood system using function. You can either specify neighbors individually or all at once. 221 | 222 | i) setNeighbors(SiteID s1, SiteID s2, EnergyTermType weight=1); 223 | Specifies neighbors individually. You must call this function exactly once for any 224 | pair of neighboring sites s1 and s2. That is if you call setNeighbors(s1,s2) then you should not call 225 | setNeighbors(s2,s1). If Vpq(l_p,l_q) = V(l_p,l_q)*w_pq, where V(l_p,l_q) is some function that 226 | depends only on the labels l_p,l_q, then specify w_pq by using: setNeighbors(p,q,w_pq). 227 | 228 | ii) To pass in all neighbor information at once, use function: 229 | 230 | void setAllNeighbors(SiteID *numNeighbors,SiteID **neighborsIndexes,EnergyTermType **neighborsWeights); 231 | Here: 232 | (a) numNeighbors is an array of size num_sites, and numNeighbors[i] is the number of neighbors for site i 233 | (b) neighborIndexes is an array of size num_pixels which stores of pointers. Namely, 234 | neighborsIndexes[i] is a pointer to the array storing the sites which are neighbors to site i 235 | 236 | (c) neighborWeights is an array of size num_sites, and neighborWeighs[i] is a pointer to array 237 | storing the weights between site i and its neighbors in the same order as neighborIndexes[i] 238 | stores the indexes of neighbors. Example: if sites i and j are neighbors, then 239 | for some k and m, neighborsIndexes[i][k] == j and neighborsIndexes[j][m] = i. Then 240 | neighborWeights[i][k] = w_ij and neighborWeights[j][m] = w_ij, where w_ij is the weight 241 | betwen neighbors i and j, that is V_ij = w_ij *V(l_i,l_j) 242 | 243 | 244 | _______________________________________________________________________________________________ 245 | 246 | 247 | 6. Setting the data and smoothness terms. 248 | 249 | I have provided many functions to set up data and smooth costs. Any setDataCost() function can 250 | be used with any setSmoothCost() function. 251 | setSmoothCostVH() can only be used with a GridGraph, due to the special structure of the input 252 | parameters that only make sense for a grid graph. 253 | 254 | 255 | ------------------------dataCostSetup----------------------- 256 | 257 | (a) void setDataCost(EnergyTermType *dataArray); 258 | dataArray is an array s.t. the data cost for pixel p and label l is stored at 259 | dataArray[pixel*num_labels+l]. If the current neighborhood system is a grid, then 260 | the data term for label l and pixel with coordinates (x,y) is stored at 261 | dataArray[(x+y*width)*num_labels + l]. Thus the size of array dataArray is num_pixels*num_labels. 262 | Can call this function only one time. 263 | 264 | (b) void setDataCost(DataCostFn fn); 265 | DataCostFn is a pointer to a function f(Pixel p, Label l), s.t. the data cost for pixel p to have 266 | label l is given by f(p,l). Can call this function only one time. 267 | 268 | (c) void setDataCost(DataCostFnExtra fn,void *extraData); 269 | DataCostFnExtra is a pointer to a function f(SiteID p, LabelID l,void *extraData), s.t. the data 270 | cost for pixel p to have label l is given by f(p,l,extraData). Can call this function only one time. 271 | 272 | 273 | (d) void setDataCost(SiteID s, LabelID l, EnergyTermType e); 274 | sets up D(s,l) = 3; You must call this function for each pixel and each label. 275 | 276 | 277 | (e) void setDataCostFunctor(DataCostFunctor* f); 278 | 279 | Experienced C++ users can subclass our DataCostFunctor base class to achieve 280 | a similar functionality as (b) or (c) above. By overriding the compute() method 281 | of DataCostFunctor, your compute() method will be called by the GCoptimization 282 | class each time a data penalty must be computed. 283 | 284 | ------------------------smoothCostSetup----------------------- 285 | 286 | 287 | (a) void setSmoothCost(EnergyTermType *V) 288 | V is an array of smoothness costs, such that V_pq(label1,label2) is stored at V[label1+num_labels*label2] 289 | If graph is a grid, then using this function only if the smooth costs are not spacially varying 290 | that is the smoothness penalty V depends only on labels, but not on sites. If the graph is 291 | not a grid, then you can specify spacially varying coefficients w_pq when you set up the 292 | neighborhood system using setNeighbor(p,q,w_pq) function. In this case, 293 | V_pq(label1,label2) = V[label1+num_labels*label2]*w_pq. This function can be called only one time. 294 | 295 | 296 | (b) void setSmoothCost(SmoothCostFn fn); 297 | 298 | fn is pointer to a function f(s1,s2,l1,l2) such that smoothness penalty for neigboring sites 299 | s1 and s2 to have labels, respectively, l1 and l2 is f(s1,s2,l1,l2). This function can be 300 | called only one time. 301 | 302 | (c) void setSmoothCost(SmoothCostFnExtra fn,void *extraData); 303 | 304 | Same as above, but can pass an extra pointer to the data needed for computation 305 | 306 | (d) void setSmoothCost(LabelID l1, LabelID l2, EnergyTermType e) 307 | 308 | sets up V(l1,l2) = e. Must call this function for each pair of labels (l1,l2). Notice 309 | that for any l1 and l2, you must call this function on (l1,l2) AND (l2,l1). 310 | V(l1,l2) has to be equal to V(l2,l1) in this case. 311 | 312 | 313 | (e) void setSmoothCostVH(EnergyTermType *V, EnergyTermType *vCosts, EnergyTermType *hCosts); 314 | 315 | This function should be used only if the graph is a grid (GCGridGraph constructor is used). 316 | Array V is the same as above, under (a). 317 | Arrays hCosts and vCosts have the same size as the image (that is width*height), and are used to set 318 | the spatially varying coefficients w_pq. If p = (x,y) and q = (x+1,y), then 319 | w_pq = hCosts[x+y*width], and so the smoothness penalty for pixels (x,y) and (x+1,y) to have labels 320 | label1 and label2, that is V_pq(label1,label2) = V[label1+num_labels*label2]*hCosts[x+y*width] 321 | If p = (x,y) and q = (x,y+q), then 322 | w_pq = vCosts[x+y*width], and so the smoothness penalty for pixels (x,y) and (x,y+1) to have labels 323 | label1 and label2, that is V_pq(label1,label2) = V[label1+num_labels*label2]*vCosts[x+y*width] 324 | This function can be only called one time. 325 | 326 | 327 | (f) void setSmoothCostFunctor(SmoothCostFunctor* f); 328 | 329 | Experienced C++ users can subclass our SmoothCostFunctor base class to achieve 330 | a similar functionality as (b) or (c) above. By overriding the compute() method 331 | of SmoothCostFunctor, your compute() method will be called by the GCoptimization 332 | class each time a smoothness penalty must be computed. 333 | 334 | ################################################################## 335 | 336 | 6. Optimizing the energy 337 | 338 | You can optimize the energy and get the resulting labeling using the following functions. Notice that they can 339 | be called as many times as one wishes after the constructor has been called and the data/smoothness terms 340 | (and the neighborhood system, if general graph) has beeen set. The initial labeling is set to consists of 341 | all 0's. Use function setLabel(SiteID pixelP, LabelID labelL), described under heading (x) in this section 342 | to initialize the labeling to anything else (but in the valid range, of course, labels must be between 343 | 0 and num_labels-1) 344 | 345 | 346 | i) expansion(int max_num_iterations) 347 | Will run the expansion algorithm up to the specified number of iterations. 348 | Returns the energy of the resulting labeling. 349 | 350 | ii)expansion(); 351 | Will run the expansion algorithm to convergence (convergence is guaranteed) 352 | Returns the energy of the resulting labeling 353 | 354 | 355 | iii) alpha_expansion(LabelID alpha_label); 356 | Performs expansion on the label specified by alpha_label. Returns the energy of the resulting labeling 357 | 358 | 359 | iV) void alpha_expansion(LabelID alpha_label, SiteID *sites, SiteID num); 360 | Peforms expansion on label alpha_label only for sites specified by *sites. 361 | num is the size of array "sites" 362 | 363 | 364 | v) swap(int max_num_iterations); 365 | Will run the swap algorithm up to the specified number of iterations. 366 | Returns the energy of the resulting labeling. 367 | 368 | 369 | vi) swap(); 370 | Will run the swap algorithm up to convergence (convergence is guaranteed) 371 | Returns the energy of the resulting labeling 372 | 373 | 374 | vii) alpha_beta_swap(LabelID alpha_label, LabelID beta_label); 375 | Performs swap on a pair of labels, specified by the input parameters alpha_label, beta_label. 376 | Returns the energy of the resulting labeling. 377 | 378 | viii)void alpha_beta_swap(LabelID alpha_label, LabelID beta_label, SiteID *alphaSites, SiteID alpha_size, 379 | SiteID *betaSites, SiteID beta_size); 380 | 381 | Peforms swap on a pair of labels, specified by the input parameters alpha_label, beta_label 382 | only on the sites in the specified arrays, alphaSites and betaSites, and the array sizes 383 | are, respectively, alpha_size and beta_size 384 | 385 | 386 | 387 | ix) whatLabel(SiteID site) 388 | Returns the current label assigned to site. Can be called at any time after the constructor call. 389 | 390 | x) setLabel(SiteID s, LabelID l) 391 | Sets the label of site s to the the input parameter l. Can be called at any time after the constructor call. 392 | This function is useful, in particular, to initialize the labeling to something specific before optimization 393 | starts. 394 | 395 | xi) setLabelOrder(bool RANDOM_LABEL_ORDER) 396 | By default, the labels for the swap and expansion algorithms are visited in not random order, 397 | but random label visitation might give better results. To set the label order to 398 | be not random, call setLabelOrder(false). To set it to be random, call setLabelOrder(true). Notice, 399 | that by using functions under heading (iii) and (vii) you can completely and exactly specify the desired 400 | order on labels. 401 | 402 | xii) EnergyType giveDataEnergy(); 403 | Returns the data part of the energy of the current labling 404 | 405 | 406 | xiii) EnergyType giveSmoothEnergy(); 407 | Returns the smoothness part of the energy of the current labling 408 | 409 | 410 | ################################################################## 411 | 412 | 7. Example usage. 413 | 414 | 415 | See example.cpp 416 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/LICENSE.TXT: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/LinkedBlockList.cpp: -------------------------------------------------------------------------------- 1 | #include "LinkedBlockList.h" 2 | #include 3 | #include 4 | 5 | /*********************************************************************/ 6 | 7 | void LinkedBlockList::addFront(ListType item) { 8 | 9 | if ( m_head_block_size == GCLL_BLOCK_SIZE ) 10 | { 11 | LLBlock *tmp = (LLBlock *) new LLBlock; 12 | if ( !tmp ) {printf("\nOut of memory");exit(1);} 13 | tmp -> m_next = m_head; 14 | m_head = tmp; 15 | m_head_block_size = 0; 16 | } 17 | 18 | m_head ->m_item[m_head_block_size] = item; 19 | m_head_block_size++; 20 | } 21 | 22 | /*********************************************************************/ 23 | 24 | ListType LinkedBlockList::next() 25 | { 26 | ListType toReturn = m_cursor -> m_item[m_cursor_ind]; 27 | 28 | m_cursor_ind++; 29 | 30 | if ( m_cursor == m_head && m_cursor_ind >= m_head_block_size ) 31 | { 32 | m_cursor = m_cursor ->m_next; 33 | m_cursor_ind = 0; 34 | } 35 | else if ( m_cursor_ind == GCLL_BLOCK_SIZE ) 36 | { 37 | m_cursor = m_cursor ->m_next; 38 | m_cursor_ind = 0; 39 | } 40 | return(toReturn); 41 | } 42 | 43 | /*********************************************************************/ 44 | 45 | bool LinkedBlockList::hasNext() 46 | { 47 | if ( m_cursor != 0 ) return (true); 48 | else return(false); 49 | } 50 | 51 | 52 | /*********************************************************************/ 53 | 54 | LinkedBlockList::~LinkedBlockList() 55 | { 56 | LLBlock *tmp; 57 | 58 | while ( m_head != 0 ) 59 | { 60 | tmp = m_head; 61 | m_head = m_head->m_next; 62 | delete tmp; 63 | } 64 | }; 65 | 66 | /*********************************************************************/ 67 | 68 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/LinkedBlockList.h: -------------------------------------------------------------------------------- 1 | /* Singly Linked List of Blocks */ 2 | // This data structure should be used only for the GCoptimization class implementation 3 | // because it lucks some important general functions for general list, like remove_item() 4 | // The head block may be not full 5 | // For regular 2D grids, it's better to set GCLL_BLOCK_SIZE to 2 6 | // For other graphs, it should be set to the average expected number of neighbors 7 | // Data in linked list for the neighborhood system is allocated in blocks of size GCLL_BLOCK_SIZE 8 | 9 | #ifndef __LINKEDBLOCKLIST_H__ 10 | #define __LINKEDBLOCKLIST_H__ 11 | 12 | #define GCLL_BLOCK_SIZE 4 13 | // GCLL_BLOCKSIZE should "fit" into the type BlockType. That is 14 | // if GCLL_BLOCKSIZE is larger than 255 but smaller than largest short integer 15 | // then BlockType should be set to short 16 | typedef char BlockType; 17 | 18 | //The type of data stored in the linked list 19 | typedef void * ListType; 20 | 21 | class LinkedBlockList{ 22 | 23 | public: 24 | void addFront(ListType item); 25 | inline bool isEmpty(){if (m_head == 0) return(true); else return(false);}; 26 | inline LinkedBlockList(){m_head = 0; m_head_block_size = GCLL_BLOCK_SIZE;}; 27 | ~LinkedBlockList(); 28 | 29 | // Next three functins are for the linked list traversal 30 | inline void setCursorFront(){m_cursor = m_head; m_cursor_ind = 0;}; 31 | ListType next(); 32 | bool hasNext(); 33 | 34 | private: 35 | typedef struct LLBlockStruct{ 36 | ListType m_item[GCLL_BLOCK_SIZE]; 37 | struct LLBlockStruct *m_next; 38 | } LLBlock; 39 | 40 | LLBlock *m_head; 41 | // Remembers the number of elements in the head block, since it may not be full 42 | BlockType m_head_block_size; 43 | // For block traversal, points to current element in the current block 44 | BlockType m_cursor_ind; 45 | // For block traversal, points to current block in the linked list 46 | LLBlock *m_cursor; 47 | }; 48 | 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : GCoptimization Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this GCoptimization application for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your GCoptimization application. 9 | 10 | 11 | GCoptimization.vcproj 12 | This is the main project file for VC++ projects generated using an Application Wizard. 13 | It contains information about the version of Visual C++ that generated the file, and 14 | information about the platforms, configurations, and project features selected with the 15 | Application Wizard. 16 | 17 | GCoptimization.cpp 18 | This is the main application source file. 19 | 20 | ///////////////////////////////////////////////////////////////////////////// 21 | Other standard files: 22 | 23 | StdAfx.h, StdAfx.cpp 24 | These files are used to build a precompiled header (PCH) file 25 | named GCoptimization.pch and a precompiled types file named StdAfx.obj. 26 | 27 | ///////////////////////////////////////////////////////////////////////////// 28 | Other notes: 29 | 30 | AppWizard uses "TODO:" comments to indicate parts of the source code you 31 | should add to or customize. 32 | 33 | ///////////////////////////////////////////////////////////////////////////// 34 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/block.h: -------------------------------------------------------------------------------- 1 | /* block.h */ 2 | /* 3 | Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | 21 | /* 22 | Template classes Block and DBlock 23 | Implement adding and deleting items of the same type in blocks. 24 | 25 | If there there are many items then using Block or DBlock 26 | is more efficient than using 'new' and 'delete' both in terms 27 | of memory and time since 28 | (1) On some systems there is some minimum amount of memory 29 | that 'new' can allocate (e.g., 64), so if items are 30 | small that a lot of memory is wasted. 31 | (2) 'new' and 'delete' are designed for items of varying size. 32 | If all items has the same size, then an algorithm for 33 | adding and deleting can be made more efficient. 34 | (3) All Block and DBlock functions are inline, so there are 35 | no extra function calls. 36 | 37 | Differences between Block and DBlock: 38 | (1) DBlock allows both adding and deleting items, 39 | whereas Block allows only adding items. 40 | (2) Block has an additional operation of scanning 41 | items added so far (in the order in which they were added). 42 | (3) Block allows to allocate several consecutive 43 | items at a time, whereas DBlock can add only a single item. 44 | 45 | Note that no constructors or destructors are called for items. 46 | 47 | Example usage for items of type 'MyType': 48 | 49 | /////////////////////////////////////////////////// 50 | #include "block.h" 51 | #define BLOCK_SIZE 1024 52 | typedef struct { int a, b; } MyType; 53 | MyType *ptr, *array[10000]; 54 | 55 | ... 56 | 57 | Block *block = new Block(BLOCK_SIZE); 58 | 59 | // adding items 60 | for (int i=0; i New(); 63 | ptr -> a = ptr -> b = rand(); 64 | } 65 | 66 | // reading items 67 | for (ptr=block->ScanFirst(); ptr; ptr=block->ScanNext()) 68 | { 69 | printf("%d %d\n", ptr->a, ptr->b); 70 | } 71 | 72 | delete block; 73 | 74 | ... 75 | 76 | DBlock *dblock = new DBlock(BLOCK_SIZE); 77 | 78 | // adding items 79 | for (int i=0; i New(); 82 | } 83 | 84 | // deleting items 85 | for (int i=0; i Delete(array[i]); 88 | } 89 | 90 | // adding items 91 | for (int i=0; i New(); 94 | } 95 | 96 | delete dblock; 97 | 98 | /////////////////////////////////////////////////// 99 | 100 | Note that DBlock deletes items by marking them as 101 | empty (i.e., by adding them to the list of free items), 102 | so that this memory could be used for subsequently 103 | added items. Thus, at each moment the memory allocated 104 | is determined by the maximum number of items allocated 105 | simultaneously at earlier moments. All memory is 106 | deallocated only when the destructor is called. 107 | */ 108 | 109 | #ifndef __BLOCK_H__ 110 | #define __BLOCK_H__ 111 | 112 | #include 113 | 114 | /***********************************************************************/ 115 | /***********************************************************************/ 116 | /***********************************************************************/ 117 | 118 | template class Block 119 | { 120 | public: 121 | /* Constructor. Arguments are the block size and 122 | (optionally) the pointer to the function which 123 | will be called if allocation failed; the message 124 | passed to this function is "Not enough memory!" */ 125 | Block(int size, void (*err_function)(char *) = NULL) { first = last = NULL; block_size = size; error_function = err_function; } 126 | 127 | /* Destructor. Deallocates all items added so far */ 128 | ~Block() { while (first) { block *next = first -> next; delete first; first = next; } } 129 | 130 | /* Allocates 'num' consecutive items; returns pointer 131 | to the first item. 'num' cannot be greater than the 132 | block size since items must fit in one block */ 133 | Type *New(int num = 1) 134 | { 135 | Type *t; 136 | 137 | if (!last || last->current + num > last->last) 138 | { 139 | if (last && last->next) last = last -> next; 140 | else 141 | { 142 | block *next = (block *) new char [sizeof(block) + (block_size-1)*sizeof(Type)]; 143 | if (!next) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 144 | if (last) last -> next = next; 145 | else first = next; 146 | last = next; 147 | last -> current = & ( last -> data[0] ); 148 | last -> last = last -> current + block_size; 149 | last -> next = NULL; 150 | } 151 | } 152 | 153 | t = last -> current; 154 | last -> current += num; 155 | return t; 156 | } 157 | 158 | /* Returns the first item (or NULL, if no items were added) */ 159 | Type *ScanFirst() 160 | { 161 | scan_current_block = first; 162 | if (!scan_current_block) return NULL; 163 | scan_current_data = & ( scan_current_block -> data[0] ); 164 | return scan_current_data ++; 165 | } 166 | 167 | /* Returns the next item (or NULL, if all items have been read) 168 | Can be called only if previous ScanFirst() or ScanNext() 169 | call returned not NULL. */ 170 | Type *ScanNext() 171 | { 172 | if (scan_current_data >= scan_current_block -> current) 173 | { 174 | scan_current_block = scan_current_block -> next; 175 | if (!scan_current_block) return NULL; 176 | scan_current_data = & ( scan_current_block -> data[0] ); 177 | } 178 | return scan_current_data ++; 179 | } 180 | 181 | /* Marks all elements as empty */ 182 | void Reset() 183 | { 184 | block *b; 185 | if (!first) return; 186 | for (b=first; ; b=b->next) 187 | { 188 | b -> current = & ( b -> data[0] ); 189 | if (b == last) break; 190 | } 191 | last = first; 192 | } 193 | 194 | /***********************************************************************/ 195 | 196 | private: 197 | 198 | typedef struct block_st 199 | { 200 | Type *current, *last; 201 | struct block_st *next; 202 | Type data[1]; 203 | } block; 204 | 205 | int block_size; 206 | block *first; 207 | block *last; 208 | 209 | block *scan_current_block; 210 | Type *scan_current_data; 211 | 212 | void (*error_function)(char *); 213 | }; 214 | 215 | /***********************************************************************/ 216 | /***********************************************************************/ 217 | /***********************************************************************/ 218 | 219 | template class DBlock 220 | { 221 | public: 222 | /* Constructor. Arguments are the block size and 223 | (optionally) the pointer to the function which 224 | will be called if allocation failed; the message 225 | passed to this function is "Not enough memory!" */ 226 | DBlock(int size, void (*err_function)(char *) = NULL) { first = NULL; first_free = NULL; block_size = size; error_function = err_function; } 227 | 228 | /* Destructor. Deallocates all items added so far */ 229 | ~DBlock() { while (first) { block *next = first -> next; delete [] first; first = next; } } 230 | 231 | /* Allocates one item */ 232 | Type *New() 233 | { 234 | block_item *item; 235 | 236 | if (!first_free) 237 | { 238 | block *next = first; 239 | first = (block *) new char [sizeof(block) + (block_size-1)*sizeof(block_item)]; 240 | if (!first) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 241 | first_free = & (first -> data[0] ); 242 | for (item=first_free; item next_free = item + 1; 244 | item -> next_free = NULL; 245 | first -> next = next; 246 | } 247 | 248 | item = first_free; 249 | first_free = item -> next_free; 250 | return (Type *) item; 251 | } 252 | 253 | /* Deletes an item allocated previously */ 254 | void Delete(Type *t) 255 | { 256 | ((block_item *) t) -> next_free = first_free; 257 | first_free = (block_item *) t; 258 | } 259 | 260 | /***********************************************************************/ 261 | 262 | private: 263 | 264 | typedef union block_item_st 265 | { 266 | Type t; 267 | block_item_st *next_free; 268 | } block_item; 269 | 270 | typedef struct block_st 271 | { 272 | struct block_st *next; 273 | block_item data[1]; 274 | } block; 275 | 276 | int block_size; 277 | block *first; 278 | block_item *first_free; 279 | 280 | void (*error_function)(char *); 281 | }; 282 | 283 | 284 | #endif 285 | 286 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/energy.h: -------------------------------------------------------------------------------- 1 | /* energy.h */ 2 | /* Vladimir Kolmogorov (vnk@cs.cornell.edu), 2003. */ 3 | 4 | /* 5 | This software implements an energy minimization technique described in 6 | 7 | What Energy Functions can be Minimized via Graph Cuts? 8 | Vladimir Kolmogorov and Ramin Zabih. 9 | To appear in IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI). 10 | Earlier version appeared in European Conference on Computer Vision (ECCV), May 2002. 11 | 12 | More specifically, it computes the global minimum of a function E of binary 13 | variables x_1, ..., x_n which can be written as a sum of terms involving 14 | at most three variables at a time: 15 | 16 | E(x_1, ..., x_n) = \sum_{i} E^{i} (x_i) 17 | + \sum_{i,j} E^{i,j} (x_i, x_j) 18 | + \sum_{i,j,k} E^{i,j,k}(x_i, x_j, x_k) 19 | 20 | The method works only if each term is "regular". Definitions of regularity 21 | for terms E^{i}, E^{i,j}, E^{i,j,k} are given below as comments to functions 22 | add_term1(), add_term2(), add_term3(). 23 | 24 | This software can be used only for research purposes. IF YOU USE THIS SOFTWARE, 25 | YOU SHOULD CITE THE AFOREMENTIONED PAPER IN ANY RESULTING PUBLICATION. 26 | 27 | In order to use it, you will also need a MAXFLOW software which can be 28 | obtained from http://www.cs.cornell.edu/People/vnk/software.html 29 | 30 | 31 | Example usage 32 | (Minimizes the following function of 3 binary variables: 33 | E(x, y, z) = x - 2*y + 3*(1-z) - 4*x*y + 5*|y-z|): 34 | 35 | /////////////////////////////////////////////////// 36 | 37 | #include 38 | #include "energy.h" 39 | 40 | void main() 41 | { 42 | // Minimize the following function of 3 binary variables: 43 | // E(x, y, z) = x - 2*y + 3*(1-z) - 4*x*y + 5*|y-z| 44 | 45 | Energy::Var varx, vary, varz; 46 | Energy *e = new Energy(); 47 | 48 | varx = e -> add_variable(); 49 | vary = e -> add_variable(); 50 | varz = e -> add_variable(); 51 | 52 | e -> add_term1(varx, 0, 1); // add term x 53 | e -> add_term1(vary, 0, -2); // add term -2*y 54 | e -> add_term1(varz, 3, 0); // add term 3*(1-z) 55 | 56 | e -> add_term2(x, y, 0, 0, 0, -4); // add term -4*x*y 57 | e -> add_term2(y, z, 0, 5, 5, 0); // add term 5*|y-z| 58 | 59 | Energy::TotalValue Emin = e -> minimize(); 60 | 61 | printf("Minimum = %d\n", Emin); 62 | printf("Optimal solution:\n"); 63 | printf("x = %d\n", e->get_var(varx)); 64 | printf("y = %d\n", e->get_var(vary)); 65 | printf("z = %d\n", e->get_var(varz)); 66 | 67 | delete e; 68 | } 69 | 70 | /////////////////////////////////////////////////// 71 | */ 72 | 73 | #ifndef __ENERGY_H__ 74 | #define __ENERGY_H__ 75 | 76 | #include 77 | #include "graph.h" 78 | 79 | class Energy : Graph 80 | { 81 | public: 82 | typedef node_id Var; 83 | 84 | /* Types of energy values. 85 | Value is a type of a value in a single term 86 | TotalValue is a type of a value of the total energy. 87 | By default Value = short, TotalValue = int. 88 | To change it, change the corresponding types in graph.h */ 89 | typedef captype Value; 90 | typedef flowtype TotalValue; 91 | 92 | /* interface functions */ 93 | 94 | /* Constructor. Optional argument is the pointer to the 95 | function which will be called if an error occurs; 96 | an error message is passed to this function. If this 97 | argument is omitted, exit(1) will be called. */ 98 | Energy(void (*err_function)(char *) = NULL); 99 | 100 | /* Destructor */ 101 | ~Energy(); 102 | 103 | /* Adds a new binary variable */ 104 | Var add_variable(); 105 | 106 | /* Adds a constant E to the energy function */ 107 | void add_constant(Value E); 108 | 109 | /* Adds a new term E(x) of one binary variable 110 | to the energy function, where 111 | E(0) = E0, E(1) = E1 112 | E0 and E1 can be arbitrary */ 113 | void add_term1(Var x, 114 | Value E0, Value E1); 115 | 116 | /* Adds a new term E(x,y) of two binary variables 117 | to the energy function, where 118 | E(0,0) = E00, E(0,1) = E01 119 | E(1,0) = E10, E(1,1) = E11 120 | The term must be regular, i.e. E00 + E11 <= E01 + E10 */ 121 | void add_term2(Var x, Var y, 122 | Value E00, Value E01, 123 | Value E10, Value E11); 124 | 125 | /* Adds a new term E(x,y,z) of three binary variables 126 | to the energy function, where 127 | E(0,0,0) = E000, E(0,0,1) = E001 128 | E(0,1,0) = E010, E(0,1,1) = E011 129 | E(1,0,0) = E100, E(1,0,1) = E101 130 | E(1,1,0) = E110, E(1,1,1) = E111 131 | The term must be regular. It means that if one 132 | of the variables is fixed (for example, y=1), then 133 | the resulting function of two variables must be regular. 134 | Since there are 6 ways to fix one variable 135 | (3 variables times 2 binary values - 0 and 1), 136 | this is equivalent to 6 inequalities */ 137 | void add_term3(Var x, Var y, Var z, 138 | Value E000, Value E001, 139 | Value E010, Value E011, 140 | Value E100, Value E101, 141 | Value E110, Value E111); 142 | 143 | /* After the energy function has been constructed, 144 | call this function to minimize it. 145 | Returns the minimum of the function */ 146 | TotalValue minimize(); 147 | 148 | /* After 'minimize' has been called, this function 149 | can be used to determine the value of variable 'x' 150 | in the optimal solution. 151 | Returns either 0 or 1 */ 152 | int get_var(Var x); 153 | 154 | /***********************************************************************/ 155 | /***********************************************************************/ 156 | /***********************************************************************/ 157 | 158 | private: 159 | /* internal variables and functions */ 160 | 161 | TotalValue Econst; 162 | void (*error_function)(char *); /* this function is called if a error occurs, 163 | with a corresponding error message 164 | (or exit(1) is called if it's NULL) */ 165 | }; 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | /***********************************************************************/ 182 | /************************ Implementation ******************************/ 183 | /***********************************************************************/ 184 | 185 | inline Energy::Energy(void (*err_function)(char *)) : Graph(err_function) 186 | { 187 | Econst = 0; 188 | error_function = err_function; 189 | } 190 | 191 | inline Energy::~Energy() {} 192 | 193 | inline Energy::Var Energy::add_variable() { return add_node(); } 194 | 195 | inline void Energy::add_constant(Value A) { Econst += A; } 196 | 197 | inline void Energy::add_term1(Var x, 198 | Value A, Value B) 199 | { 200 | add_tweights(x, B, A); 201 | } 202 | 203 | inline void Energy::add_term2(Var x, Var y, 204 | Value A, Value B, 205 | Value C, Value D) 206 | { 207 | /* 208 | E = A A + 0 B-A 209 | D D C-D 0 210 | Add edges for the first term 211 | */ 212 | add_tweights(x, D, A); 213 | B -= A; C -= D; 214 | 215 | /* now need to represent 216 | 0 B 217 | C 0 218 | */ 219 | 220 | //assert(B + C >= 0); /* check regularity */ 221 | if (B < 0) 222 | { 223 | /* Write it as 224 | B B + -B 0 + 0 0 225 | 0 0 -B 0 B+C 0 226 | */ 227 | add_tweights(x, 0, B); /* first term */ 228 | add_tweights(y, 0, -B); /* second term */ 229 | add_edge(x, y, 0, B+C); /* third term */ 230 | } 231 | else if (C < 0) 232 | { 233 | /* Write it as 234 | -C -C + C 0 + 0 B+C 235 | 0 0 C 0 0 0 236 | */ 237 | add_tweights(x, 0, -C); /* first term */ 238 | add_tweights(y, 0, C); /* second term */ 239 | add_edge(x, y, B+C, 0); /* third term */ 240 | } 241 | else /* B >= 0, C >= 0 */ 242 | { 243 | add_edge(x, y, B, C); 244 | } 245 | } 246 | 247 | inline void Energy::add_term3(Var x, Var y, Var z, 248 | Value E000, Value E001, 249 | Value E010, Value E011, 250 | Value E100, Value E101, 251 | Value E110, Value E111) 252 | { 253 | register Value pi = (E000 + E011 + E101 + E110) - (E100 + E010 + E001 + E111); 254 | register Value delta; 255 | register Var u; 256 | 257 | if (pi >= 0) 258 | { 259 | Econst += E111 - (E011 + E101 + E110); 260 | 261 | add_tweights(x, E101, E001); 262 | add_tweights(y, E110, E100); 263 | add_tweights(z, E011, E010); 264 | 265 | delta = (E010 + E001) - (E000 + E011); /* -pi(E[x=0]) */ 266 | //assert(delta >= 0); /* check regularity */ 267 | add_edge(y, z, delta, 0); 268 | 269 | delta = (E100 + E001) - (E000 + E101); /* -pi(E[y=0]) */ 270 | //assert(delta >= 0); /* check regularity */ 271 | add_edge(z, x, delta, 0); 272 | 273 | delta = (E100 + E010) - (E000 + E110); /* -pi(E[z=0]) */ 274 | //assert(delta >= 0); /* check regularity */ 275 | add_edge(x, y, delta, 0); 276 | 277 | if (pi > 0) 278 | { 279 | u = add_variable(); 280 | add_edge(x, u, pi, 0); 281 | add_edge(y, u, pi, 0); 282 | add_edge(z, u, pi, 0); 283 | add_tweights(u, 0, pi); 284 | } 285 | } 286 | else 287 | { 288 | Econst += E000 - (E100 + E010 + E001); 289 | 290 | add_tweights(x, E110, E010); 291 | add_tweights(y, E011, E001); 292 | add_tweights(z, E101, E100); 293 | 294 | delta = (E110 + E101) - (E100 + E111); /* -pi(E[x=1]) */ 295 | //assert(delta >= 0); /* check regularity */ 296 | add_edge(z, y, delta, 0); 297 | 298 | delta = (E110 + E011) - (E010 + E111); /* -pi(E[y=1]) */ 299 | //assert(delta >= 0); /* check regularity */ 300 | add_edge(x, z, delta, 0); 301 | 302 | delta = (E101 + E011) - (E001 + E111); /* -pi(E[z=1]) */ 303 | //assert(delta >= 0); /* check regularity */ 304 | add_edge(y, x, delta, 0); 305 | 306 | u = add_variable(); 307 | add_edge(u, x, -pi, 0); 308 | add_edge(u, y, -pi, 0); 309 | add_edge(u, z, -pi, 0); 310 | add_tweights(u, -pi, 0); 311 | } 312 | } 313 | 314 | inline Energy::TotalValue Energy::minimize() { return Econst + maxflow(); } 315 | 316 | inline int Energy::get_var(Var x) { return (int) what_segment(x); } 317 | 318 | #endif 319 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/example.cpp: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////// 2 | // Example illustrating the use of GCoptimization.cpp 3 | // 4 | ///////////////////////////////////////////////////////////////////////////// 5 | // 6 | // Optimization problem: 7 | // is a set of sites (pixels) of width 10 and hight 5. Thus number of pixels is 50 8 | // grid neighborhood: each pixel has its left, right, up, and bottom pixels as neighbors 9 | // 7 labels 10 | // Data costs: D(pixel,label) = 0 if pixel < 25 and label = 0 11 | // : D(pixel,label) = 10 if pixel < 25 and label is not 0 12 | // : D(pixel,label) = 0 if pixel >= 25 and label = 5 13 | // : D(pixel,label) = 10 if pixel >= 25 and label is not 5 14 | // Smoothness costs: V(p1,p2,l1,l2) = min( (l1-l2)*(l1-l2) , 4 ) 15 | // Below in the main program, we illustrate different ways of setting data and smoothness costs 16 | // that our interface allow and solve this optimizaiton problem 17 | 18 | // For most of the examples, we use no spatially varying pixel dependent terms. 19 | // For some examples, to demonstrate spatially varying terms we use 20 | // V(p1,p2,l1,l2) = w_{p1,p2}*[min((l1-l2)*(l1-l2),4)], with 21 | // w_{p1,p2} = p1+p2 if |p1-p2| == 1 and w_{p1,p2} = p1*p2 if |p1-p2| is not 1 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "GCoptimization.h" 29 | 30 | 31 | struct ForDataFn{ 32 | int numLab; 33 | int *data; 34 | }; 35 | 36 | 37 | int smoothFn(int p1, int p2, int l1, int l2) 38 | { 39 | if ( (l1-l2)*(l1-l2) <= 4 ) return((l1-l2)*(l1-l2)); 40 | else return(4); 41 | } 42 | 43 | int dataFn(int p, int l, void *data) 44 | { 45 | ForDataFn *myData = (ForDataFn *) data; 46 | int numLab = myData->numLab; 47 | 48 | return( myData->data[p*numLab+l] ); 49 | } 50 | 51 | 52 | 53 | //////////////////////////////////////////////////////////////////////////////// 54 | // smoothness and data costs are set up one by one, individually 55 | // grid neighborhood structure is assumed 56 | // 57 | void GridGraph_Individually(int width,int height,int num_pixels,int num_labels) 58 | { 59 | 60 | int *result = new int[num_pixels]; // stores result of optimization 61 | 62 | 63 | 64 | try{ 65 | GCoptimizationGridGraph *gc = new GCoptimizationGridGraph(width,height,num_labels); 66 | 67 | // first set up data costs individually 68 | for ( int i = 0; i < num_pixels; i++ ) 69 | for (int l = 0; l < num_labels; l++ ) 70 | if (i < 25 ){ 71 | if( l == 0 ) gc->setDataCost(i,l,0); 72 | else gc->setDataCost(i,l,10); 73 | } 74 | else { 75 | if( l == 5 ) gc->setDataCost(i,l,0); 76 | else gc->setDataCost(i,l,10); 77 | } 78 | 79 | // next set up smoothness costs individually 80 | for ( int l1 = 0; l1 < num_labels; l1++ ) 81 | for (int l2 = 0; l2 < num_labels; l2++ ){ 82 | int cost = (l1-l2)*(l1-l2) <= 4 ? (l1-l2)*(l1-l2):4; 83 | gc->setSmoothCost(l1,l2,cost); 84 | } 85 | 86 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 87 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 88 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 89 | 90 | for ( int i = 0; i < num_pixels; i++ ) 91 | result[i] = gc->whatLabel(i); 92 | 93 | delete gc; 94 | } 95 | catch (GCException e){ 96 | e.Report(); 97 | } 98 | 99 | delete [] result; 100 | } 101 | 102 | //////////////////////////////////////////////////////////////////////////////// 103 | // in this version, set data and smoothness terms using arrays 104 | // grid neighborhood structure is assumed 105 | // 106 | void GridGraph_DArraySArray(int width,int height,int num_pixels,int num_labels) 107 | { 108 | 109 | int *result = new int[num_pixels]; // stores result of optimization 110 | 111 | // first set up the array for data costs 112 | int *data = new int[num_pixels*num_labels]; 113 | for ( int i = 0; i < num_pixels; i++ ) 114 | for (int l = 0; l < num_labels; l++ ) 115 | if (i < 25 ){ 116 | if( l == 0 ) data[i*num_labels+l] = 0; 117 | else data[i*num_labels+l] = 10; 118 | } 119 | else { 120 | if( l == 5 ) data[i*num_labels+l] = 0; 121 | else data[i*num_labels+l] = 10; 122 | } 123 | // next set up the array for smooth costs 124 | int *smooth = new int[num_labels*num_labels]; 125 | for ( int l1 = 0; l1 < num_labels; l1++ ) 126 | for (int l2 = 0; l2 < num_labels; l2++ ) 127 | smooth[l1+l2*num_labels] = (l1-l2)*(l1-l2) <= 4 ? (l1-l2)*(l1-l2):4; 128 | 129 | 130 | try{ 131 | GCoptimizationGridGraph *gc = new GCoptimizationGridGraph(width,height,num_labels); 132 | gc->setDataCost(data); 133 | gc->setSmoothCost(smooth); 134 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 135 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 136 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 137 | 138 | for ( int i = 0; i < num_pixels; i++ ) 139 | result[i] = gc->whatLabel(i); 140 | 141 | delete gc; 142 | } 143 | catch (GCException e){ 144 | e.Report(); 145 | } 146 | 147 | delete [] result; 148 | delete [] smooth; 149 | delete [] data; 150 | 151 | } 152 | //////////////////////////////////////////////////////////////////////////////// 153 | // in this version, set data and smoothness terms using arrays 154 | // grid neighborhood structure is assumed 155 | // 156 | void GridGraph_DfnSfn(int width,int height,int num_pixels,int num_labels) 157 | { 158 | 159 | int *result = new int[num_pixels]; // stores result of optimization 160 | 161 | // first set up the array for data costs 162 | int *data = new int[num_pixels*num_labels]; 163 | for ( int i = 0; i < num_pixels; i++ ) 164 | for (int l = 0; l < num_labels; l++ ) 165 | if (i < 25 ){ 166 | if( l == 0 ) data[i*num_labels+l] = 0; 167 | else data[i*num_labels+l] = 10; 168 | } 169 | else { 170 | if( l == 5 ) data[i*num_labels+l] = 0; 171 | else data[i*num_labels+l] = 10; 172 | } 173 | 174 | 175 | try{ 176 | GCoptimizationGridGraph *gc = new GCoptimizationGridGraph(width,height,num_labels); 177 | 178 | // set up the needed data to pass to function for the data costs 179 | ForDataFn toFn; 180 | toFn.data = data; 181 | toFn.numLab = num_labels; 182 | 183 | gc->setDataCost(&dataFn,&toFn); 184 | 185 | // smoothness comes from function pointer 186 | gc->setSmoothCost(&smoothFn); 187 | 188 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 189 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 190 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 191 | 192 | for ( int i = 0; i < num_pixels; i++ ) 193 | result[i] = gc->whatLabel(i); 194 | 195 | delete gc; 196 | } 197 | catch (GCException e){ 198 | e.Report(); 199 | } 200 | 201 | delete [] result; 202 | delete [] data; 203 | 204 | } 205 | //////////////////////////////////////////////////////////////////////////////// 206 | // Uses spatially varying smoothness terms. That is 207 | // V(p1,p2,l1,l2) = w_{p1,p2}*[min((l1-l2)*(l1-l2),4)], with 208 | // w_{p1,p2} = p1+p2 if |p1-p2| == 1 and w_{p1,p2} = p1*p2 if |p1-p2| is not 1 209 | void GridGraph_DArraySArraySpatVarying(int width,int height,int num_pixels,int num_labels) 210 | { 211 | int *result = new int[num_pixels]; // stores result of optimization 212 | 213 | // first set up the array for data costs 214 | int *data = new int[num_pixels*num_labels]; 215 | for ( int i = 0; i < num_pixels; i++ ) 216 | for (int l = 0; l < num_labels; l++ ) 217 | if (i < 25 ){ 218 | if( l == 0 ) data[i*num_labels+l] = 0; 219 | else data[i*num_labels+l] = 10; 220 | } 221 | else { 222 | if( l == 5 ) data[i*num_labels+l] = 0; 223 | else data[i*num_labels+l] = 10; 224 | } 225 | // next set up the array for smooth costs 226 | int *smooth = new int[num_labels*num_labels]; 227 | for ( int l1 = 0; l1 < num_labels; l1++ ) 228 | for (int l2 = 0; l2 < num_labels; l2++ ) 229 | smooth[l1+l2*num_labels] = (l1-l2)*(l1-l2) <= 4 ? (l1-l2)*(l1-l2):4; 230 | 231 | // next set up spatially varying arrays V and H 232 | 233 | int *V = new int[num_pixels]; 234 | int *H = new int[num_pixels]; 235 | 236 | 237 | for ( int i = 0; i < num_pixels; i++ ){ 238 | H[i] = i+(i+1)%3; 239 | V[i] = i*(i+width)%7; 240 | } 241 | 242 | 243 | try{ 244 | GCoptimizationGridGraph *gc = new GCoptimizationGridGraph(width,height,num_labels); 245 | gc->setDataCost(data); 246 | gc->setSmoothCostVH(smooth,V,H); 247 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 248 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 249 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 250 | 251 | for ( int i = 0; i < num_pixels; i++ ) 252 | result[i] = gc->whatLabel(i); 253 | 254 | delete gc; 255 | } 256 | catch (GCException e){ 257 | e.Report(); 258 | } 259 | 260 | delete [] result; 261 | delete [] smooth; 262 | delete [] data; 263 | delete [] V; 264 | delete [] H; 265 | 266 | 267 | } 268 | 269 | //////////////////////////////////////////////////////////////////////////////// 270 | // in this version, set data and smoothness terms using arrays 271 | // grid neighborhood is set up "manually" 272 | // 273 | void GeneralGraph_DArraySArray(int width,int height,int num_pixels,int num_labels) 274 | { 275 | 276 | int *result = new int[num_pixels]; // stores result of optimization 277 | 278 | // first set up the array for data costs 279 | int *data = new int[num_pixels*num_labels]; 280 | for ( int i = 0; i < num_pixels; i++ ) 281 | for (int l = 0; l < num_labels; l++ ) 282 | if (i < 25 ){ 283 | if( l == 0 ) data[i*num_labels+l] = 0; 284 | else data[i*num_labels+l] = 10; 285 | } 286 | else { 287 | if( l == 5 ) data[i*num_labels+l] = 0; 288 | else data[i*num_labels+l] = 10; 289 | } 290 | // next set up the array for smooth costs 291 | int *smooth = new int[num_labels*num_labels]; 292 | for ( int l1 = 0; l1 < num_labels; l1++ ) 293 | for (int l2 = 0; l2 < num_labels; l2++ ) 294 | smooth[l1+l2*num_labels] = (l1-l2)*(l1-l2) <= 4 ? (l1-l2)*(l1-l2):4; 295 | 296 | 297 | try{ 298 | GCoptimizationGeneralGraph *gc = new GCoptimizationGeneralGraph(num_pixels,num_labels); 299 | gc->setDataCost(data); 300 | gc->setSmoothCost(smooth); 301 | 302 | // now set up a grid neighborhood system 303 | // first set up horizontal neighbors 304 | for (int y = 0; y < height; y++ ) 305 | for (int x = 1; x < width; x++ ) 306 | gc->setNeighbors(x+y*width,x-1+y*width); 307 | 308 | // next set up vertical neighbors 309 | for (int y = 1; y < height; y++ ) 310 | for (int x = 0; x < width; x++ ) 311 | gc->setNeighbors(x+y*width,x+(y-1)*width); 312 | 313 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 314 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 315 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 316 | 317 | for ( int i = 0; i < num_pixels; i++ ) 318 | result[i] = gc->whatLabel(i); 319 | 320 | delete gc; 321 | } 322 | catch (GCException e){ 323 | e.Report(); 324 | } 325 | 326 | delete [] result; 327 | delete [] smooth; 328 | delete [] data; 329 | 330 | } 331 | //////////////////////////////////////////////////////////////////////////////// 332 | // in this version, set data and smoothness terms using arrays 333 | // grid neighborhood is set up "manually". Uses spatially varying terms. Namely 334 | // V(p1,p2,l1,l2) = w_{p1,p2}*[min((l1-l2)*(l1-l2),4)], with 335 | // w_{p1,p2} = p1+p2 if |p1-p2| == 1 and w_{p1,p2} = p1*p2 if |p1-p2| is not 1 336 | 337 | void GeneralGraph_DArraySArraySpatVarying(int width,int height,int num_pixels,int num_labels) 338 | { 339 | int *result = new int[num_pixels]; // stores result of optimization 340 | 341 | // first set up the array for data costs 342 | int *data = new int[num_pixels*num_labels]; 343 | for ( int i = 0; i < num_pixels; i++ ) 344 | for (int l = 0; l < num_labels; l++ ) 345 | if (i < 25 ){ 346 | if( l == 0 ) data[i*num_labels+l] = 0; 347 | else data[i*num_labels+l] = 10; 348 | } 349 | else { 350 | if( l == 5 ) data[i*num_labels+l] = 0; 351 | else data[i*num_labels+l] = 10; 352 | } 353 | // next set up the array for smooth costs 354 | int *smooth = new int[num_labels*num_labels]; 355 | for ( int l1 = 0; l1 < num_labels; l1++ ) 356 | for (int l2 = 0; l2 < num_labels; l2++ ) 357 | smooth[l1+l2*num_labels] = (l1-l2)*(l1-l2) <= 4 ? (l1-l2)*(l1-l2):4; 358 | 359 | 360 | try{ 361 | GCoptimizationGeneralGraph *gc = new GCoptimizationGeneralGraph(num_pixels,num_labels); 362 | gc->setDataCost(data); 363 | gc->setSmoothCost(smooth); 364 | 365 | // now set up a grid neighborhood system 366 | // first set up horizontal neighbors 367 | for (int y = 0; y < height; y++ ) 368 | for (int x = 1; x < width; x++ ){ 369 | int p1 = x-1+y*width; 370 | int p2 =x+y*width; 371 | gc->setNeighbors(p1,p2,p1+p2); 372 | } 373 | 374 | // next set up vertical neighbors 375 | for (int y = 1; y < height; y++ ) 376 | for (int x = 0; x < width; x++ ){ 377 | int p1 = x+(y-1)*width; 378 | int p2 =x+y*width; 379 | gc->setNeighbors(p1,p2,p1*p2); 380 | } 381 | 382 | printf("\nBefore optimization energy is %d",gc->compute_energy()); 383 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 384 | printf("\nAfter optimization energy is %d",gc->compute_energy()); 385 | 386 | for ( int i = 0; i < num_pixels; i++ ) 387 | result[i] = gc->whatLabel(i); 388 | 389 | delete gc; 390 | } 391 | catch (GCException e){ 392 | e.Report(); 393 | } 394 | 395 | delete [] result; 396 | delete [] smooth; 397 | delete [] data; 398 | 399 | 400 | } 401 | //////////////////////////////////////////////////////////////////////////////// 402 | 403 | int main(int argc, char **argv) 404 | { 405 | int width = 10; 406 | int height = 5; 407 | int num_pixels = width*height; 408 | int num_labels = 7; 409 | 410 | 411 | // smoothness and data costs are set up one by one, individually 412 | GridGraph_Individually(width,height,num_pixels,num_labels); 413 | 414 | // smoothness and data costs are set up using arrays 415 | GridGraph_DArraySArray(width,height,num_pixels,num_labels); 416 | 417 | // smoothness and data costs are set up using functions 418 | GridGraph_DfnSfn(width,height,num_pixels,num_labels); 419 | 420 | // smoothness and data costs are set up using arrays. 421 | // spatially varying terms are present 422 | GridGraph_DArraySArraySpatVarying(width,height,num_pixels,num_labels); 423 | 424 | //Will pretend our graph is 425 | //general, and set up a neighborhood system 426 | // which actually is a grid 427 | GeneralGraph_DArraySArray(width,height,num_pixels,num_labels); 428 | 429 | //Will pretend our graph is general, and set up a neighborhood system 430 | // which actually is a grid. Also uses spatially varying terms 431 | GeneralGraph_DArraySArraySpatVarying(width,height,num_pixels,num_labels); 432 | 433 | printf("\n Finished %d (%d) clock per sec %d",clock()/CLOCKS_PER_SEC,clock(),CLOCKS_PER_SEC); 434 | 435 | 436 | return 0; 437 | } 438 | 439 | ///////////////////////////////////////////////////////////////////////////////// 440 | 441 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/graph.cpp: -------------------------------------------------------------------------------- 1 | /* graph.cpp */ 2 | /* 3 | Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | 21 | #include 22 | #include "graph.h" 23 | 24 | Graph::Graph(void (*err_function)(char *)) 25 | { 26 | error_function = err_function; 27 | node_block = new Block(NODE_BLOCK_SIZE, error_function); 28 | arc_block = new Block(NODE_BLOCK_SIZE, error_function); 29 | flow = 0; 30 | } 31 | 32 | Graph::~Graph() 33 | { 34 | delete node_block; 35 | delete arc_block; 36 | } 37 | 38 | Graph::node_id Graph::add_node() 39 | { 40 | node *i = node_block -> New(); 41 | 42 | i -> first = NULL; 43 | i -> tr_cap = 0; 44 | 45 | return (node_id) i; 46 | } 47 | 48 | void Graph::add_edge(node_id from, node_id to, captype cap, captype rev_cap) 49 | { 50 | arc *a, *a_rev; 51 | 52 | a = arc_block -> New(2); 53 | a_rev = a + 1; 54 | 55 | a -> sister = a_rev; 56 | a_rev -> sister = a; 57 | a -> next = ((node*)from) -> first; 58 | ((node*)from) -> first = a; 59 | a_rev -> next = ((node*)to) -> first; 60 | ((node*)to) -> first = a_rev; 61 | a -> head = (node*)to; 62 | a_rev -> head = (node*)from; 63 | a -> r_cap = cap; 64 | a_rev -> r_cap = rev_cap; 65 | } 66 | 67 | void Graph::set_tweights(node_id i, captype cap_source, captype cap_sink) 68 | { 69 | flow += (cap_source < cap_sink) ? cap_source : cap_sink; 70 | ((node*)i) -> tr_cap = cap_source - cap_sink; 71 | } 72 | 73 | void Graph::add_tweights(node_id i, captype cap_source, captype cap_sink) 74 | { 75 | register captype delta = ((node*)i) -> tr_cap; 76 | if (delta > 0) cap_source += delta; 77 | else cap_sink -= delta; 78 | flow += (cap_source < cap_sink) ? cap_source : cap_sink; 79 | ((node*)i) -> tr_cap = cap_source - cap_sink; 80 | } 81 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/graph.h: -------------------------------------------------------------------------------- 1 | /* graph.h */ 2 | /* 3 | This software library implements the maxflow algorithm 4 | described in 5 | 6 | An Experimental Comparison of Min-Cut/Max-Flow Algorithms 7 | for Energy Minimization in Vision. 8 | Yuri Boykov and Vladimir Kolmogorov. 9 | In IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), 10 | September 2004 11 | 12 | This algorithm was developed by Yuri Boykov and Vladimir Kolmogorov 13 | at Siemens Corporate Research. To make it available for public use, 14 | it was later reimplemented by Vladimir Kolmogorov based on open publications. 15 | 16 | If you use this software for research purposes, you should cite 17 | the aforementioned paper in any resulting publication. 18 | */ 19 | 20 | /* 21 | Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). 22 | 23 | This program is free software; you can redistribute it and/or modify 24 | it under the terms of the GNU General Public License as published by 25 | the Free Software Foundation; either version 2 of the License, or 26 | (at your option) any later version. 27 | 28 | This program is distributed in the hope that it will be useful, 29 | but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | GNU General Public License for more details. 32 | 33 | You should have received a copy of the GNU General Public License 34 | along with this program; if not, write to the Free Software 35 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 36 | */ 37 | 38 | 39 | /* 40 | For description, example usage, discussion of graph representation 41 | and memory usage see README.TXT. 42 | */ 43 | 44 | #ifndef __GRAPH_H__ 45 | #define __GRAPH_H__ 46 | 47 | #include "block.h" 48 | 49 | /* 50 | Nodes, arcs and pointers to nodes are 51 | added in blocks for memory and time efficiency. 52 | Below are numbers of items in blocks 53 | */ 54 | #define NODE_BLOCK_SIZE 512 55 | #define ARC_BLOCK_SIZE 4096 56 | #define NODEPTR_BLOCK_SIZE 128 57 | 58 | class Graph 59 | { 60 | public: 61 | typedef enum 62 | { 63 | SOURCE = 0, 64 | SINK = 1 65 | } termtype; /* terminals */ 66 | 67 | /* Type of edge weights. 68 | Can be changed to char, int, float, double, ... */ 69 | typedef float captype; 70 | /* Type of total flow */ 71 | typedef float flowtype; 72 | 73 | typedef void * node_id; 74 | 75 | /* interface functions */ 76 | 77 | /* Constructor. Optional argument is the pointer to the 78 | function which will be called if an error occurs; 79 | an error message is passed to this function. If this 80 | argument is omitted, exit(1) will be called. */ 81 | Graph(void (*err_function)(char *) = NULL); 82 | 83 | /* Destructor */ 84 | ~Graph(); 85 | 86 | /* Adds a node to the graph */ 87 | node_id add_node(); 88 | 89 | /* Adds a bidirectional edge between 'from' and 'to' 90 | with the weights 'cap' and 'rev_cap' */ 91 | void add_edge(node_id from, node_id to, captype cap, captype rev_cap); 92 | 93 | /* Sets the weights of the edges 'SOURCE->i' and 'i->SINK' 94 | Can be called at most once for each node before any call to 'add_tweights'. 95 | Weights can be negative */ 96 | void set_tweights(node_id i, captype cap_source, captype cap_sink); 97 | 98 | /* Adds new edges 'SOURCE->i' and 'i->SINK' with corresponding weights 99 | Can be called multiple times for each node. 100 | Weights can be negative */ 101 | void add_tweights(node_id i, captype cap_source, captype cap_sink); 102 | 103 | /* After the maxflow is computed, this function returns to which 104 | segment the node 'i' belongs (Graph::SOURCE or Graph::SINK) */ 105 | termtype what_segment(node_id i); 106 | 107 | /* Computes the maxflow. Can be called only once. */ 108 | flowtype maxflow(); 109 | 110 | /***********************************************************************/ 111 | /***********************************************************************/ 112 | /***********************************************************************/ 113 | 114 | private: 115 | /* internal variables and functions */ 116 | 117 | struct arc_st; 118 | 119 | /* node structure */ 120 | typedef struct node_st 121 | { 122 | arc_st *first; /* first outcoming arc */ 123 | 124 | arc_st *parent; /* node's parent */ 125 | node_st *next; /* pointer to the next active node 126 | (or to itself if it is the last node in the list) */ 127 | int TS; /* timestamp showing when DIST was computed */ 128 | int DIST; /* distance to the terminal */ 129 | short is_sink; /* flag showing whether the node is in the source or in the sink tree */ 130 | 131 | captype tr_cap; /* if tr_cap > 0 then tr_cap is residual capacity of the arc SOURCE->node 132 | otherwise -tr_cap is residual capacity of the arc node->SINK */ 133 | } node; 134 | 135 | /* arc structure */ 136 | typedef struct arc_st 137 | { 138 | node_st *head; /* node the arc points to */ 139 | arc_st *next; /* next arc with the same originating node */ 140 | arc_st *sister; /* reverse arc */ 141 | 142 | captype r_cap; /* residual capacity */ 143 | } arc; 144 | 145 | /* 'pointer to node' structure */ 146 | typedef struct nodeptr_st 147 | { 148 | node_st *ptr; 149 | nodeptr_st *next; 150 | } nodeptr; 151 | 152 | Block *node_block; 153 | Block *arc_block; 154 | DBlock *nodeptr_block; 155 | 156 | void (*error_function)(char *); /* this function is called if a error occurs, 157 | with a corresponding error message 158 | (or exit(1) is called if it's NULL) */ 159 | 160 | flowtype flow; /* total flow */ 161 | 162 | /***********************************************************************/ 163 | 164 | node *queue_first[2], *queue_last[2]; /* list of active nodes */ 165 | nodeptr *orphan_first, *orphan_last; /* list of pointers to orphans */ 166 | int TIME; /* monotonically increasing global counter */ 167 | 168 | /***********************************************************************/ 169 | 170 | /* functions for processing active list */ 171 | void set_active(node *i); 172 | node *next_active(); 173 | 174 | void maxflow_init(); 175 | void augment(arc *middle_arc); 176 | void process_source_orphan(node *i); 177 | void process_sink_orphan(node *i); 178 | }; 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /semantic_segmentation/GCMex/maxflow.cpp: -------------------------------------------------------------------------------- 1 | /* maxflow.cpp */ 2 | /* 3 | Copyright 2001 Vladimir Kolmogorov (vnk@cs.cornell.edu), Yuri Boykov (yuri@csd.uwo.ca). 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | 21 | #include 22 | #include "graph.h" 23 | 24 | /* 25 | special constants for node->parent 26 | */ 27 | #define TERMINAL ( (arc *) 1 ) /* to terminal */ 28 | #define ORPHAN ( (arc *) 2 ) /* orphan */ 29 | 30 | #define INFINITE_D 1000000000 /* infinite distance to the terminal */ 31 | 32 | /***********************************************************************/ 33 | 34 | /* 35 | Functions for processing active list. 36 | i->next points to the next node in the list 37 | (or to i, if i is the last node in the list). 38 | If i->next is NULL iff i is not in the list. 39 | 40 | There are two queues. Active nodes are added 41 | to the end of the second queue and read from 42 | the front of the first queue. If the first queue 43 | is empty, it is replaced by the second queue 44 | (and the second queue becomes empty). 45 | */ 46 | 47 | inline void Graph::set_active(node *i) 48 | { 49 | if (!i->next) 50 | { 51 | /* it's not in the list yet */ 52 | if (queue_last[1]) queue_last[1] -> next = i; 53 | else queue_first[1] = i; 54 | queue_last[1] = i; 55 | i -> next = i; 56 | } 57 | } 58 | 59 | /* 60 | Returns the next active node. 61 | If it is connected to the sink, it stays in the list, 62 | otherwise it is removed from the list 63 | */ 64 | inline Graph::node * Graph::next_active() 65 | { 66 | node *i; 67 | 68 | while ( 1 ) 69 | { 70 | if (!(i=queue_first[0])) 71 | { 72 | queue_first[0] = i = queue_first[1]; 73 | queue_last[0] = queue_last[1]; 74 | queue_first[1] = NULL; 75 | queue_last[1] = NULL; 76 | if (!i) return NULL; 77 | } 78 | 79 | /* remove it from the active list */ 80 | if (i->next == i) queue_first[0] = queue_last[0] = NULL; 81 | else queue_first[0] = i -> next; 82 | i -> next = NULL; 83 | 84 | /* a node in the list is active iff it has a parent */ 85 | if (i->parent) return i; 86 | } 87 | } 88 | 89 | /***********************************************************************/ 90 | 91 | void Graph::maxflow_init() 92 | { 93 | node *i; 94 | 95 | queue_first[0] = queue_last[0] = NULL; 96 | queue_first[1] = queue_last[1] = NULL; 97 | orphan_first = NULL; 98 | 99 | for (i=node_block->ScanFirst(); i; i=node_block->ScanNext()) 100 | { 101 | i -> next = NULL; 102 | i -> TS = 0; 103 | if (i->tr_cap > 0) 104 | { 105 | /* i is connected to the source */ 106 | i -> is_sink = 0; 107 | i -> parent = TERMINAL; 108 | set_active(i); 109 | i -> TS = 0; 110 | i -> DIST = 1; 111 | } 112 | else if (i->tr_cap < 0) 113 | { 114 | /* i is connected to the sink */ 115 | i -> is_sink = 1; 116 | i -> parent = TERMINAL; 117 | set_active(i); 118 | i -> TS = 0; 119 | i -> DIST = 1; 120 | } 121 | else 122 | { 123 | i -> parent = NULL; 124 | } 125 | } 126 | TIME = 0; 127 | } 128 | 129 | /***********************************************************************/ 130 | 131 | void Graph::augment(arc *middle_arc) 132 | { 133 | node *i; 134 | arc *a; 135 | captype bottleneck; 136 | nodeptr *np; 137 | 138 | 139 | /* 1. Finding bottleneck capacity */ 140 | /* 1a - the source tree */ 141 | bottleneck = middle_arc -> r_cap; 142 | for (i=middle_arc->sister->head; ; i=a->head) 143 | { 144 | a = i -> parent; 145 | if (a == TERMINAL) break; 146 | if (bottleneck > a->sister->r_cap) bottleneck = a -> sister -> r_cap; 147 | } 148 | if (bottleneck > i->tr_cap) bottleneck = i -> tr_cap; 149 | /* 1b - the sink tree */ 150 | for (i=middle_arc->head; ; i=a->head) 151 | { 152 | a = i -> parent; 153 | if (a == TERMINAL) break; 154 | if (bottleneck > a->r_cap) bottleneck = a -> r_cap; 155 | } 156 | if (bottleneck > - i->tr_cap) bottleneck = - i -> tr_cap; 157 | 158 | 159 | /* 2. Augmenting */ 160 | /* 2a - the source tree */ 161 | middle_arc -> sister -> r_cap += bottleneck; 162 | middle_arc -> r_cap -= bottleneck; 163 | for (i=middle_arc->sister->head; ; i=a->head) 164 | { 165 | a = i -> parent; 166 | if (a == TERMINAL) break; 167 | a -> r_cap += bottleneck; 168 | a -> sister -> r_cap -= bottleneck; 169 | if (!a->sister->r_cap) 170 | { 171 | /* add i to the adoption list */ 172 | i -> parent = ORPHAN; 173 | np = nodeptr_block -> New(); 174 | np -> ptr = i; 175 | np -> next = orphan_first; 176 | orphan_first = np; 177 | } 178 | } 179 | i -> tr_cap -= bottleneck; 180 | if (!i->tr_cap) 181 | { 182 | /* add i to the adoption list */ 183 | i -> parent = ORPHAN; 184 | np = nodeptr_block -> New(); 185 | np -> ptr = i; 186 | np -> next = orphan_first; 187 | orphan_first = np; 188 | } 189 | /* 2b - the sink tree */ 190 | for (i=middle_arc->head; ; i=a->head) 191 | { 192 | a = i -> parent; 193 | if (a == TERMINAL) break; 194 | a -> sister -> r_cap += bottleneck; 195 | a -> r_cap -= bottleneck; 196 | if (!a->r_cap) 197 | { 198 | /* add i to the adoption list */ 199 | i -> parent = ORPHAN; 200 | np = nodeptr_block -> New(); 201 | np -> ptr = i; 202 | np -> next = orphan_first; 203 | orphan_first = np; 204 | } 205 | } 206 | i -> tr_cap += bottleneck; 207 | if (!i->tr_cap) 208 | { 209 | /* add i to the adoption list */ 210 | i -> parent = ORPHAN; 211 | np = nodeptr_block -> New(); 212 | np -> ptr = i; 213 | np -> next = orphan_first; 214 | orphan_first = np; 215 | } 216 | 217 | 218 | flow += bottleneck; 219 | } 220 | 221 | /***********************************************************************/ 222 | 223 | void Graph::process_source_orphan(node *i) 224 | { 225 | node *j; 226 | arc *a0, *a0_min = NULL, *a; 227 | nodeptr *np; 228 | int d, d_min = INFINITE_D; 229 | 230 | /* trying to find a new parent */ 231 | for (a0=i->first; a0; a0=a0->next) 232 | if (a0->sister->r_cap) 233 | { 234 | j = a0 -> head; 235 | if (!j->is_sink && (a=j->parent)) 236 | { 237 | /* checking the origin of j */ 238 | d = 0; 239 | while ( 1 ) 240 | { 241 | if (j->TS == TIME) 242 | { 243 | d += j -> DIST; 244 | break; 245 | } 246 | a = j -> parent; 247 | d ++; 248 | if (a==TERMINAL) 249 | { 250 | j -> TS = TIME; 251 | j -> DIST = 1; 252 | break; 253 | } 254 | if (a==ORPHAN) { d = INFINITE_D; break; } 255 | j = a -> head; 256 | } 257 | if (dhead; j->TS!=TIME; j=j->parent->head) 266 | { 267 | j -> TS = TIME; 268 | j -> DIST = d --; 269 | } 270 | } 271 | } 272 | } 273 | 274 | if (i->parent = a0_min) 275 | { 276 | i -> TS = TIME; 277 | i -> DIST = d_min + 1; 278 | } 279 | else 280 | { 281 | /* no parent is found */ 282 | i -> TS = 0; 283 | 284 | /* process neighbors */ 285 | for (a0=i->first; a0; a0=a0->next) 286 | { 287 | j = a0 -> head; 288 | if (!j->is_sink && (a=j->parent)) 289 | { 290 | if (a0->sister->r_cap) set_active(j); 291 | if (a!=TERMINAL && a!=ORPHAN && a->head==i) 292 | { 293 | /* add j to the adoption list */ 294 | j -> parent = ORPHAN; 295 | np = nodeptr_block -> New(); 296 | np -> ptr = j; 297 | if (orphan_last) orphan_last -> next = np; 298 | else orphan_first = np; 299 | orphan_last = np; 300 | np -> next = NULL; 301 | } 302 | } 303 | } 304 | } 305 | } 306 | 307 | void Graph::process_sink_orphan(node *i) 308 | { 309 | node *j; 310 | arc *a0, *a0_min = NULL, *a; 311 | nodeptr *np; 312 | int d, d_min = INFINITE_D; 313 | 314 | /* trying to find a new parent */ 315 | for (a0=i->first; a0; a0=a0->next) 316 | if (a0->r_cap) 317 | { 318 | j = a0 -> head; 319 | if (j->is_sink && (a=j->parent)) 320 | { 321 | /* checking the origin of j */ 322 | d = 0; 323 | while ( 1 ) 324 | { 325 | if (j->TS == TIME) 326 | { 327 | d += j -> DIST; 328 | break; 329 | } 330 | a = j -> parent; 331 | d ++; 332 | if (a==TERMINAL) 333 | { 334 | j -> TS = TIME; 335 | j -> DIST = 1; 336 | break; 337 | } 338 | if (a==ORPHAN) { d = INFINITE_D; break; } 339 | j = a -> head; 340 | } 341 | if (dhead; j->TS!=TIME; j=j->parent->head) 350 | { 351 | j -> TS = TIME; 352 | j -> DIST = d --; 353 | } 354 | } 355 | } 356 | } 357 | 358 | if (i->parent = a0_min) 359 | { 360 | i -> TS = TIME; 361 | i -> DIST = d_min + 1; 362 | } 363 | else 364 | { 365 | /* no parent is found */ 366 | i -> TS = 0; 367 | 368 | /* process neighbors */ 369 | for (a0=i->first; a0; a0=a0->next) 370 | { 371 | j = a0 -> head; 372 | if (j->is_sink && (a=j->parent)) 373 | { 374 | if (a0->r_cap) set_active(j); 375 | if (a!=TERMINAL && a!=ORPHAN && a->head==i) 376 | { 377 | /* add j to the adoption list */ 378 | j -> parent = ORPHAN; 379 | np = nodeptr_block -> New(); 380 | np -> ptr = j; 381 | if (orphan_last) orphan_last -> next = np; 382 | else orphan_first = np; 383 | orphan_last = np; 384 | np -> next = NULL; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | 391 | /***********************************************************************/ 392 | 393 | Graph::flowtype Graph::maxflow() 394 | { 395 | node *i, *j, *current_node = NULL; 396 | arc *a; 397 | nodeptr *np, *np_next; 398 | 399 | maxflow_init(); 400 | nodeptr_block = new DBlock(NODEPTR_BLOCK_SIZE, error_function); 401 | 402 | while ( 1 ) 403 | { 404 | if (i=current_node) 405 | { 406 | i -> next = NULL; /* remove active flag */ 407 | if (!i->parent) i = NULL; 408 | } 409 | if (!i) 410 | { 411 | if (!(i = next_active())) break; 412 | } 413 | 414 | /* growth */ 415 | if (!i->is_sink) 416 | { 417 | /* grow source tree */ 418 | for (a=i->first; a; a=a->next) 419 | if (a->r_cap) 420 | { 421 | j = a -> head; 422 | if (!j->parent) 423 | { 424 | j -> is_sink = 0; 425 | j -> parent = a -> sister; 426 | j -> TS = i -> TS; 427 | j -> DIST = i -> DIST + 1; 428 | set_active(j); 429 | } 430 | else if (j->is_sink) break; 431 | else if (j->TS <= i->TS && 432 | j->DIST > i->DIST) 433 | { 434 | /* heuristic - trying to make the distance from j to the source shorter */ 435 | j -> parent = a -> sister; 436 | j -> TS = i -> TS; 437 | j -> DIST = i -> DIST + 1; 438 | } 439 | } 440 | } 441 | else 442 | { 443 | /* grow sink tree */ 444 | for (a=i->first; a; a=a->next) 445 | if (a->sister->r_cap) 446 | { 447 | j = a -> head; 448 | if (!j->parent) 449 | { 450 | j -> is_sink = 1; 451 | j -> parent = a -> sister; 452 | j -> TS = i -> TS; 453 | j -> DIST = i -> DIST + 1; 454 | set_active(j); 455 | } 456 | else if (!j->is_sink) { a = a -> sister; break; } 457 | else if (j->TS <= i->TS && 458 | j->DIST > i->DIST) 459 | { 460 | /* heuristic - trying to make the distance from j to the sink shorter */ 461 | j -> parent = a -> sister; 462 | j -> TS = i -> TS; 463 | j -> DIST = i -> DIST + 1; 464 | } 465 | } 466 | } 467 | 468 | TIME ++; 469 | 470 | if (a) 471 | { 472 | i -> next = i; /* set active flag */ 473 | current_node = i; 474 | 475 | /* augmentation */ 476 | augment(a); 477 | /* augmentation end */ 478 | 479 | /* adoption */ 480 | while (np=orphan_first) 481 | { 482 | np_next = np -> next; 483 | np -> next = NULL; 484 | 485 | while (np=orphan_first) 486 | { 487 | orphan_first = np -> next; 488 | i = np -> ptr; 489 | nodeptr_block -> Delete(np); 490 | if (!orphan_first) orphan_last = NULL; 491 | if (i->is_sink) process_sink_orphan(i); 492 | else process_source_orphan(i); 493 | } 494 | 495 | orphan_first = np_next; 496 | } 497 | /* adoption end */ 498 | } 499 | else current_node = NULL; 500 | } 501 | 502 | delete nodeptr_block; 503 | 504 | return flow; 505 | } 506 | 507 | /***********************************************************************/ 508 | 509 | Graph::termtype Graph::what_segment(node_id i) 510 | { 511 | if (((node*)i)->parent && !((node*)i)->is_sink) return SOURCE; 512 | return SINK; 513 | } 514 | 515 | -------------------------------------------------------------------------------- /semantic_segmentation/README.md: -------------------------------------------------------------------------------- 1 | ### Semantic Segmentation 2 | 3 | In here we incorporate a graph cuts formulation with per-point part probability used as a unary term and the boundary probabilities 4 | and/or normal angle differences. For the graph-cuts implementation we are using the [GCoptimization Library Version 2.3](https://cs.uwaterloo.ca/~oveksler/OldCode.html) 5 | along with [MATLAB Engine API for Python](https://www.mathworks.com/help/matlab/matlab_external/install-the-matlab-engine-for-python.html). 6 | You can use the ```semantic_segmentation.py``` script as follows 7 | ```bash 8 | python semantic_segmentation.py --dataset --category --semantic_model_path \ 9 | --boundary_model_path --pairwise_features 10 | # = e.g. ../datasets/partnet_dataset/h5/sem_seg_poisson_h5_release 11 | # = e.g Bag 12 | # = e.g. ../trained_models/partnet_sem_seg_models/Bag/model.ckpt 13 | # = e.g. ../trained_models/partnet_boundary_detection_models/Bag/model.ckpt 14 | # = (1) normal_angles, (2) boundary_confidence, (3) combine (weighted combination of (1) and (2) 15 | ``` 16 | 17 | 18 | -------------------------------------------------------------------------------- /semantic_segmentation/graphCuts.m: -------------------------------------------------------------------------------- 1 | function YP = graphCuts(point_cloud, gcuts_smoothing) 2 | % MAP with graph cuts 3 | 4 | addpath('GCMex'); 5 | N = size(point_cloud.unary_term, 1); 6 | K = size(point_cloud.unary_term, 2); 7 | SmoothnessCost = gcuts_smoothing * single( 1 - eye( K ) ); 8 | SparseSmoothness = double( point_cloud.adjP ); 9 | SparseSmoothness(point_cloud.adjpi) = point_cloud.PT; 10 | 11 | %% create graph 12 | YP = GCMex( zeros(1, N), single(point_cloud.unary_term'), SparseSmoothness, SmoothnessCost, 1); -------------------------------------------------------------------------------- /semantic_segmentation/pointCloudObject.m: -------------------------------------------------------------------------------- 1 | function B = pointCloudObject_(point_cloud) 2 | 3 | B = struct(); 4 | % Add xyz coordinates 5 | B.points = point_cloud(:, 1:3); 6 | % Add normals 7 | if size(point_cloud, 2) == 6 8 | B.normals = point_cloud(:, 4:6); 9 | end 10 | % Add boundary confidence 11 | if size(point_cloud, 2) == 4 12 | B.boundary_confidence = point_cloud(:, 4); 13 | end 14 | % Add normals + boundary confidence 15 | if size(point_cloud, 2) == 7 16 | B.normals = point_cloud(:, 4:6); 17 | B.boundary_confidence = point_cloud(:, 7); 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /semantic_segmentation/point_cloud_seg.m: -------------------------------------------------------------------------------- 1 | function sem_seg_graph_cut = point_cloud_seg_(point_cloud_data, sem_fc_output, gcuts_smoothing, pt_features, pt_arg) 2 | % Semantic segmentation of point cloud using graph cuts 3 | 4 | %% Check input arguments 5 | 6 | if nargin < 4 7 | error('Too few input arguments'); 8 | end 9 | 10 | assert(size(point_cloud_data, 2) >= 3, 'point_cloud_data array should have at least 3 columns'); 11 | assert(size(point_cloud_data, 1) == size(sem_fc_output, 1), ... 12 | 'point_cloud_data #points=%d != sem_fc_output #points=%d', ... 13 | size(point_cloud_data, 1), size(sem_fc_output, 1)); 14 | 15 | pt_feat_mode = 1; 16 | if strcmp(pt_features, 'boundary_confidence') 17 | pt_feat_mode = 1; 18 | elseif strcmp(pt_features, 'normal_angles') 19 | pt_feat_mode = 2; 20 | elseif strcmp(pt_features, 'combine') 21 | % Use boundary confidence and normal angles 22 | pt_feat_mode = 3; 23 | else 24 | warning('Unknown pairwise feature; boundary confidence will be used intsead'); 25 | end 26 | 27 | if (pt_feat_mode == 1) || (pt_feat_mode == 3) 28 | % Choose pooling method 29 | if nargin == 5 && isa(pt_arg, 'char') 30 | if strcmp(pt_arg, 'min') 31 | pool_func=@min; 32 | elseif strcmp(pt_arg, 'max') 33 | pool_func=@max; 34 | elseif strcmp(pt_arg, 'mean') 35 | pool_func=@mean; 36 | else 37 | error('Unknown pooling method: %s', pooling); 38 | end 39 | else 40 | error('Invalid pooling method') 41 | end 42 | end 43 | 44 | if pt_feat_mode == 3 45 | % Check if two smmothing factors are given 46 | if (size(gcuts_smoothing, 2) ~= 2) || (size(gcuts_smoothing, 1) ~= 1) 47 | error('Provide 2 smoothing factors'); 48 | end 49 | end 50 | 51 | %% Create point cloud object 52 | point_cloud = pointCloudObject(point_cloud_data); 53 | 54 | %% Create ajdacency matrix 55 | 56 | % Find k-nearest neighbors for each point in point cloud 57 | k = 4; 58 | [nn_idx, ~] = knnsearch(point_cloud.points, point_cloud.points, 'k', k+1); % k+1 --> the 1-nn of each point is itself 59 | 60 | % Create adjP 61 | point_cloud.adjP = logical(sparse(size(point_cloud.points, 1), size(point_cloud.points,1))); 62 | 63 | for i = 1:size(nn_idx, 1) 64 | point_cloud.adjP(i, nn_idx(i, 2:end)) = 1; 65 | end 66 | 67 | % Find non-zero elements of adjP 68 | point_cloud.adjpi = find(point_cloud.adjP); 69 | 70 | %% Graph cuts 71 | 72 | % Calculate unary term 73 | point_cloud.unary_term = -log(min(sem_fc_output+eps, 1)); 74 | 75 | % Calculate pairwise term 76 | point_cloud.PT = zeros(length(point_cloud.adjpi), 1, 'single'); 77 | [adjI, adjJ] = ind2sub(size(point_cloud.adjP), point_cloud.adjpi); 78 | if pt_feat_mode == 1 79 | point_cloud.PT = -log(min(pool_func(point_cloud.boundary_confidence(adjI), point_cloud.boundary_confidence(adjJ))+eps, 1)); 80 | elseif pt_feat_mode == 2 81 | point_cloud.PT = -log(min((acos(dot(point_cloud.normals(adjI, 1:3), point_cloud.normals(adjJ, 1:3), 2))/(pi/2)), 1) + eps); 82 | elseif pt_feat_mode == 3 83 | pt1 = -log(min((acos(dot(point_cloud.normals(adjI, 1:3), point_cloud.normals(adjJ, 1:3), 2))/(pi/2)), 1) + eps); 84 | pt2 = -log(min(pool_func(point_cloud.boundary_confidence(adjI), point_cloud.boundary_confidence(adjJ))+eps, 1)); 85 | point_cloud.PT = gcuts_smoothing(1) * pt1 + gcuts_smoothing(2) * pt2; 86 | end 87 | 88 | if pt_feat_mode == 3 89 | % Smoothing factors are already added 90 | sem_seg_graph_cut = graphCuts(point_cloud, 1); 91 | else 92 | sem_seg_graph_cut = graphCuts(point_cloud, gcuts_smoothing); 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /semantic_segmentation/semantic_segmentation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use graph cuts for point cloud semantic segmentation and compare against 3 | DGCNN semantic segmentation 4 | """ 5 | 6 | import tensorflow as tf 7 | import numpy as np 8 | import importlib 9 | import time 10 | import errno 11 | import os 12 | import sys 13 | import matlab.engine 14 | from scipy import spatial 15 | from progressbar import ProgressBar 16 | 17 | # Custom packages 18 | BASE_DIR = os.path.abspath(os.pardir) 19 | sys.path.append(BASE_DIR) 20 | sys.path.append(os.path.join(BASE_DIR, "models")) 21 | 22 | from PartNet_data import dataset_h5 as partnet_dataset_h5 23 | from utils import evaluation_utils, rotations3D 24 | from evaluation import evaluate_boundary_detection 25 | 26 | def get_sem_model(model, model_path, num_point, n_features, n_classes=1, gpu_index=0): 27 | with tf.Graph().as_default(): 28 | with tf.device('/gpu:' + str(gpu_index)): 29 | pointclouds_ph = tf.placeholder(tf.float32, shape=(1, num_point, n_features)) 30 | is_training_ph = tf.placeholder(tf.bool, shape=()) 31 | 32 | seg_pred = model.get_model(point_cloud=pointclouds_ph, is_training=is_training_ph, normals=None, 33 | use_local_frame=False, add_normals=False, bn=True, bn_decay=None, 34 | align_pointclouds=False, n_classes=n_classes) 35 | 36 | probs = tf.nn.softmax(seg_pred) 37 | saver = tf.train.Saver() 38 | 39 | # Create a session 40 | config = tf.ConfigProto() 41 | config.gpu_options.allow_growth = True 42 | config.allow_soft_placement = True 43 | config.gpu_options.visible_device_list = str(gpu_index) 44 | sess = tf.Session(config=config) 45 | 46 | # Restore variables from disk. 47 | saver.restore(sess, model_path) 48 | 49 | ops = {'pointclouds_ph': pointclouds_ph, 50 | 'is_training_ph': is_training_ph, 51 | 'seg_pred': seg_pred, 52 | 'probs': probs} 53 | 54 | return sess, ops 55 | 56 | 57 | def sem_inference(sess, ops, pc): 58 | is_training = False 59 | 60 | # Infer part labels 61 | feed_dict = {ops["pointclouds_ph"]: pc, 62 | ops["is_training_ph"]: is_training} 63 | 64 | seg_probs_res, fc_output = sess.run([ops["probs"], ops["seg_pred"]], feed_dict=feed_dict) 65 | 66 | return np.squeeze(seg_probs_res), np.squeeze(fc_output) 67 | 68 | 69 | 70 | if __name__ == '__main__': 71 | t1 = time.time() 72 | 73 | import argparse 74 | parser = argparse.ArgumentParser() 75 | parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') 76 | parser.add_argument('--num_point', type=int, default=10000, help='Point Number [default: 10000]') 77 | parser.add_argument('--category', default='Bag', help='Which single class to test on [default: Bag]') 78 | parser.add_argument('--semantic_model_path', default='', help='semantic model checkpoint file path') 79 | parser.add_argument('--boundary_model_path', default='', help='boundary model checkpoint file path') 80 | parser.add_argument('--dataset', default='', help="Specify a semantic dataset") 81 | parser.add_argument('--split', default='test', help='Choose which split of the dataset to use [default: test]') 82 | parser.add_argument('--pairwise_features', default='combine', help='Specify pairwise features for graph cuts [default: combine]') 83 | 84 | ARGS = parser.parse_args() 85 | print(vars(ARGS)) 86 | 87 | # For noise addition 88 | rng1 = np.random.RandomState(2) 89 | rng2 = np.random.RandomState(3) 90 | rng3 = np.random.RandomState(4) 91 | 92 | # Start matlab engine in the background 93 | future = matlab.engine.start_matlab(async=True) 94 | 95 | # Configuration 96 | SEMANTIC_MODEL_PATH = ARGS.semantic_model_path 97 | BOUNDARY_MODEL_PATH = ARGS.boundary_model_path 98 | GPU_INDEX = ARGS.gpu 99 | NUM_POINT = ARGS.num_point 100 | MODEL = importlib.import_module("dgcnn_part_seg") # import network module 101 | DATASET_DIR = ARGS.dataset 102 | CATEGORY = ARGS.category 103 | SPLIT = ARGS.split 104 | 105 | EVAL_FOLDER = os.path.join("evaluation_metrics", CATEGORY) 106 | try: 107 | os.makedirs(EVAL_FOLDER) 108 | except OSError as e: 109 | if e.errno != errno.EEXIST: 110 | raise 111 | 112 | # Graph cuts settings 113 | if ARGS.pairwise_features.lower() == 'boundary_confidence': 114 | PAIRWISE_FEATURES = 'boundary_confidence' 115 | elif ARGS.pairwise_features.lower() == 'normal_angles': 116 | PAIRWISE_FEATURES = 'normal_angles' 117 | elif ARGS.pairwise_features.lower() == 'combine': 118 | PAIRWISE_FEATURES = 'combine' 119 | else: 120 | print("Warning: unknown pairwise features {pt_feat:s}".format(pt_feat=ARGS.pairwise_features)) 121 | print("Boundary confidence will be used instead") 122 | PAIRWISE_FEATURES = 'boundary_confidence' 123 | 124 | # Import semantic test split 125 | DATASET = partnet_dataset_h5.Dataset(pc_dataset_dir=DATASET_DIR, category=CATEGORY, split=SPLIT, 126 | sampling=False, npoints=-1, rotate_principals_around_normal=True, evaluate=True) 127 | # Number of segmentation classes 128 | NUM_SEMANTIC_CLASSES = len(DATASET.segClasses[ARGS.category]) 129 | 130 | # Create matlab instance 131 | eng = future.result() 132 | # Add current directory to matlab path 133 | eng.addpath(os.curdir) 134 | 135 | # Gcuts_smoothing 136 | if PAIRWISE_FEATURES == "combine": 137 | gcuts_smoothing = [1.0, 1.0] 138 | else: 139 | gcuts_smoothing = 1.0 140 | 141 | 142 | # Load semantic model 143 | semantic_sess, semantic_ops = get_sem_model(model=MODEL, model_path=SEMANTIC_MODEL_PATH, num_point=NUM_POINT, n_features=3, 144 | n_classes=NUM_SEMANTIC_CLASSES, gpu_index=GPU_INDEX) 145 | 146 | # Load model 147 | boundary_sess, boundary_ops = evaluate_boundary_detection.get_model(model=MODEL, model_path=BOUNDARY_MODEL_PATH, 148 | num_point=NUM_POINT, n_features=9, add_normals=True, 149 | align_pointclouds=False, rotate_principals_around_normal=True, 150 | gpu_index=GPU_INDEX) 151 | 152 | # Init metrics for sem seg model 153 | # Init mIoU 154 | shape_iou_tot = 0.0; shape_iou_cnt = 0 155 | part_intersect = np.zeros((NUM_SEMANTIC_CLASSES), dtype=np.float32) 156 | part_union = np.zeros((NUM_SEMANTIC_CLASSES), dtype=np.float32) 157 | # Init chamfer distance 158 | overall_dcd = 0; exclude_models = 0 159 | # Init boundary IoU 160 | boundary_iou = 0.0 161 | # Init precision, recall metrics 162 | precision = 0.0; recall = 0.0; f_score = 0.0 163 | 164 | # Init metrics for graph cuts 165 | # Init mIoU 166 | shape_iou_tot_graph_cuts = 0.0; shape_iou_cnt_graph_cuts = 0 167 | part_intersect_graph_cuts = np.zeros(( NUM_SEMANTIC_CLASSES), dtype=np.float32) 168 | part_union_graph_cuts = np.zeros((NUM_SEMANTIC_CLASSES), dtype=np.float32) 169 | # Init chamfer distance 170 | overall_dcd_graph_cuts = 0.0 171 | # Init boundary IoU 172 | boundary_iou_graph_cuts = 0.0 173 | precision_graph_cuts = 0.0; recall_graph_cuts = 0.0; f_score_graph_cuts = 0.0 174 | 175 | bar = ProgressBar() 176 | for i in bar(range(len(DATASET))): 177 | 178 | # Get pointclouds from dataset 179 | points, sem_input_features, sem_pointcloud_features = \ 180 | evaluation_utils.get_pointcloud(dataset=DATASET, idx=i, n_features=3, get_tangent_vectors=False) 181 | _, boundary_input_features, boundary_pointcloud_features = \ 182 | evaluation_utils.get_pointcloud(dataset=DATASET, idx=i, n_features=9, get_tangent_vectors=True) 183 | 184 | print("Infer part labels and boundaries on point cloud ({current:d}/{total:d}) with {n_points:d} points, from category " 185 | "{cat:s} ({dataset:s}) ...".format(current=i + 1, dataset=os.path.basename(DATASET_DIR), 186 | n_points=len(points), total=len(DATASET), cat=CATEGORY)) 187 | 188 | # Add noise 189 | points = evaluation_utils.jitter_points(points=points, sigma=0.005, clip=0.01, rng=rng1) 190 | sem_input_features[:, 0:3] = np.copy(points) 191 | boundary_input_features[:, 0:3] = np.copy(points) 192 | sem_pointcloud_features[:, 0:3], R_noise = \ 193 | evaluation_utils.jitter_direction_vectors(sem_pointcloud_features[:, 0:3], sigma=1.5, clip=3, rng1=rng2, 194 | rng2=rng3, return_noise=True) 195 | boundary_pointcloud_features[:, 0:3] = np.copy(sem_pointcloud_features[:, 0:3]) 196 | boundary_input_features[:, 3:6] = np.squeeze(np.matmul(R_noise, boundary_input_features[:, 3:6, np.newaxis])) 197 | boundary_input_features[:, 6:9] = np.squeeze(np.matmul(R_noise, boundary_input_features[:, 6:9, np.newaxis])) 198 | 199 | # Semantic inference 200 | semantic_probs, semantic_fc_output = sem_inference(sess=semantic_sess, ops=semantic_ops, pc=np.expand_dims(sem_input_features, 0)) 201 | semantic_segp = np.squeeze(np.argmax(semantic_probs[:, 1:], axis=-1) + 1) 202 | 203 | 204 | # Boundary inference 205 | normals = np.expand_dims(boundary_pointcloud_features[:, 0:3], axis=0) 206 | boundary_probs, boundary_fc_output = evaluate_boundary_detection.inference(sess=boundary_sess, ops=boundary_ops, 207 | pc=np.expand_dims(boundary_input_features, 0), 208 | normals=normals, rotate_principals_around_normal=True) 209 | 210 | # Get data for graph cuts 211 | seg_points = points 212 | seg_semantic = sem_pointcloud_features[:, -1] 213 | segp_semantic = semantic_segp 214 | seg_normals = sem_pointcloud_features[:, :3] 215 | seg_boundary_probs = boundary_probs 216 | seg_sem_fc_output = semantic_probs[:, 1:] 217 | 218 | # Evaluate semantic segmentation performance wrt. shape and part category mIoU 219 | shape_iou_tot, shape_iou_cnt, part_intersect, part_union \ 220 | = evaluation_utils.mIoU_calculation(seg=seg_semantic, segp=segp_semantic.astype(np.int32), 221 | shape_iou_tot=shape_iou_tot, 222 | shape_iou_cnt=shape_iou_cnt, part_intersect=part_intersect, 223 | part_union=part_union, num_semantic_classes=NUM_SEMANTIC_CLASSES) 224 | 225 | # Infer boundaries from semantic segmentation 226 | segp_semantic[seg_semantic == 0] = 0 227 | inferred_boundary_seg = evaluation_utils.boundary_annotation(points=seg_points, semantic_seg=seg_semantic) 228 | inferred_boundary_segp = evaluation_utils.boundary_annotation(points=seg_points, semantic_seg=segp_semantic) 229 | 230 | # Check if segmentations have only 1 part or have disconnected parts 231 | seg_1_parts = np.unique(seg_semantic) 232 | seg_1_boundaries = np.unique(inferred_boundary_seg) 233 | if (len(seg_1_parts) == 1) or (len(seg_1_boundaries) == 1): 234 | # Ground truth segmentation -> exclude from evaluation 235 | exclude_models += 1 236 | else: 237 | seg_2_parts = np.unique(segp_semantic) 238 | seg_2_boundaries = np.unique(inferred_boundary_segp) 239 | if (len(seg_2_parts) == 1) or (len(seg_2_boundaries) == 1): 240 | # Predicted segmentation -> penalize for chamfer distance 241 | overall_dcd += evaluation_utils.pc_bounding_box_diagonal(seg_points) 242 | else: 243 | # Evaluate inferred boundaries wrt. chamfer distance 244 | dcd = evaluation_utils.chamfer_distance(segmentation1=np.squeeze(inferred_boundary_seg), 245 | segmentation2=np.squeeze(inferred_boundary_segp), 246 | points=seg_points) 247 | overall_dcd += dcd 248 | 249 | # Create kd-tree for surface points 250 | points_KDTree = spatial.cKDTree(seg_points, copy_data=False, balanced_tree=False, compact_nodes=False) 251 | # Find maximum sampling distance 252 | nn_dist, _ = points_KDTree.query(seg_points, k=2) 253 | nn_dist = nn_dist[:, 1] 254 | max_dist = np.amax(nn_dist) 255 | # Boundary distance tolerance threshold 256 | dist = max_dist 257 | 258 | # Calculate precision 259 | boundary_points_gt = seg_points[np.squeeze(inferred_boundary_seg==1)] 260 | if len(boundary_points_gt): 261 | boundary_points_pred = seg_points[np.squeeze(inferred_boundary_segp==1)] 262 | shape_precision, TP_precision, FP_precision = evaluation_utils.precision(boundary_points_gt=boundary_points_gt, 263 | boundary_points_pred=boundary_points_pred, 264 | dist=dist) 265 | precision += shape_precision 266 | 267 | # Calculate recall 268 | shape_recall, TP_recall, FN_recall = evaluation_utils.recall(boundary_points_gt=boundary_points_gt, 269 | boundary_points_pred=boundary_points_pred, 270 | dist=dist) 271 | recall += shape_recall 272 | 273 | # Calculate bIoU 274 | try: 275 | boundary_iou += ((TP_precision + TP_recall) / float(len(boundary_points_gt) + len(boundary_points_pred))) 276 | except: 277 | boundary_iou += 0.0 278 | 279 | # Use graph cuts for point cloud semantic segmentation 280 | semantic_seg_graph_cuts = np.zeros((seg_points.shape[0])) 281 | boundary_seg_graph_cuts = np.zeros((seg_points.shape[0])) 282 | print("Semantic segmentation of point cloud ({current:d}/{total:d}) with {nPoints:d} points, from category " 283 | "{cat:s} ({dataset:s}), using graph cuts (pairwise term: {pt_feat:s}) ..." 284 | .format(current=i + 1, dataset=os.path.basename(DATASET_DIR), nPoints=len(seg_points), 285 | total=len(DATASET), cat=CATEGORY, pt_feat=PAIRWISE_FEATURES)) 286 | 287 | # Create matlab.arrays 288 | semantic_data = seg_points 289 | if PAIRWISE_FEATURES == 'boundary_confidence': 290 | semantic_data = np.hstack((semantic_data, seg_boundary_probs[..., np.newaxis])) 291 | pt_arg = 'max' 292 | elif PAIRWISE_FEATURES == 'normal_angles': 293 | semantic_data = np.hstack((semantic_data, seg_normals)) 294 | pt_arg = '' 295 | elif PAIRWISE_FEATURES == 'combine': 296 | semantic_data = np.hstack((semantic_data, seg_normals, seg_boundary_probs[..., np.newaxis])) 297 | pt_arg = 'max' 298 | else: 299 | print("ERROR: undefined pairwise feature {pt_feat:s}" .format(pt_feat=PAIRWISE_FEATURES)) 300 | exit(-1) 301 | 302 | point_cloud_data = matlab.double([[p for p in row] for row in semantic_data]) 303 | sem_fc_output = matlab.double([[p for p in fc_out] for fc_out in seg_sem_fc_output]) 304 | 305 | # Call point_cloud_seg 306 | if PAIRWISE_FEATURES == 'combine': 307 | gcuts_smoothing_data = matlab.double([gcuts_smoothing[0], gcuts_smoothing[1]]) 308 | matlab_out = eng.point_cloud_seg(point_cloud_data, sem_fc_output, gcuts_smoothing_data, PAIRWISE_FEATURES, 309 | pt_arg) 310 | else: 311 | matlab_out = eng.point_cloud_seg(point_cloud_data, sem_fc_output, gcuts_smoothing, PAIRWISE_FEATURES, 312 | pt_arg) 313 | semantic_seg_graph_cuts = np.squeeze(np.asarray(matlab_out))+1 314 | 315 | # Evaluate graph cuts semantic segmentation performance wrt. shape and part category mIoU 316 | shape_iou_tot_graph_cuts, shape_iou_cnt_graph_cuts, part_intersect_graph_cuts, part_union_graph_cuts \ 317 | = evaluation_utils.mIoU_calculation(seg=seg_semantic, segp=semantic_seg_graph_cuts.astype(np.int32), 318 | shape_iou_tot=shape_iou_tot_graph_cuts, shape_iou_cnt=shape_iou_cnt_graph_cuts, 319 | part_intersect=part_intersect_graph_cuts, part_union=part_union_graph_cuts, 320 | num_semantic_classes=NUM_SEMANTIC_CLASSES) 321 | 322 | # Infer boundaries from semantic segmentation 323 | semantic_seg_graph_cuts[seg_semantic == 0] = 0 324 | inferred_boundary_seg_graph_cuts = evaluation_utils.boundary_annotation(points=seg_points, 325 | semantic_seg=semantic_seg_graph_cuts) 326 | boundary_seg_graph_cuts = np.squeeze(inferred_boundary_seg_graph_cuts) 327 | 328 | penalize = False 329 | if (len(seg_1_parts) == 1) or (len(seg_1_boundaries) == 1): 330 | # Ground truth segmentation -> exclude from evaluation 331 | pass 332 | else: 333 | seg_2_parts = np.unique(semantic_seg_graph_cuts) 334 | seg_2_boundaries = np.unique(inferred_boundary_seg_graph_cuts) 335 | if (len(seg_2_parts) == 1) or (len(seg_2_boundaries) == 1): 336 | # Use unary term only 337 | inferred_boundary_seg_graph_cuts = inferred_boundary_segp 338 | seg_2_boundaries = np.unique(inferred_boundary_seg_graph_cuts) 339 | if len(seg_2_boundaries) == 1: 340 | # Predicted segmentation -> penalize for chamfer distance 341 | overall_dcd_graph_cuts += evaluation_utils.pc_bounding_box_diagonal(seg_points) 342 | penalize = True 343 | if not penalize: 344 | # Calculate chamfer distance 345 | dcd = evaluation_utils.chamfer_distance(segmentation1=inferred_boundary_seg, 346 | segmentation2=inferred_boundary_seg_graph_cuts, 347 | points=seg_points) 348 | overall_dcd_graph_cuts += dcd 349 | 350 | if len(boundary_points_gt): 351 | # Calculate precision 352 | boundary_points_pred_graph_cuts = seg_points[np.squeeze(inferred_boundary_seg_graph_cuts == 1)] 353 | shape_precision_graph_cuts, TP_precision_graph_cuts, FP_precision_graph_cuts = \ 354 | evaluation_utils.precision(boundary_points_gt=boundary_points_gt, 355 | boundary_points_pred=boundary_points_pred_graph_cuts, dist=dist) 356 | precision_graph_cuts += shape_precision_graph_cuts 357 | 358 | # Calculate recall 359 | shape_recall_graph_cuts, TP_recall_graph_cuts, FN_recall_graph_cuts = \ 360 | evaluation_utils.recall(boundary_points_gt=boundary_points_gt, 361 | boundary_points_pred=boundary_points_pred_graph_cuts, dist=dist) 362 | recall_graph_cuts += shape_recall_graph_cuts 363 | 364 | # Calculate bIoU 365 | try: 366 | boundary_iou_graph_cuts += ((TP_precision_graph_cuts + TP_recall_graph_cuts) / 367 | float(len(boundary_points_gt) + len(boundary_points_pred_graph_cuts))) 368 | except: 369 | boundary_iou_graph_cuts += 0.0 370 | 371 | # Close session 372 | semantic_sess.close() 373 | boundary_sess.close() 374 | 375 | # Stop matlab engine 376 | eng.quit() 377 | 378 | # Log evaluation 379 | total_seen = len(DATASET) 380 | buf = "DGCNN\n----------\n" 381 | # Log precision 382 | precision /= float(total_seen - exclude_models) 383 | buf += 'Precision: %f\n' % (precision) 384 | # Log recall 385 | recall /= float(total_seen - exclude_models) 386 | buf += 'Recall: %f\n' % (recall) 387 | # Log f-score 388 | try: 389 | f_score = 2.0 * (precision * recall) / (precision + recall) 390 | except ZeroDivisionError: 391 | f_score = 0.0 392 | buf += 'F-score: %f\n' % (f_score) 393 | # Log category mIoU 394 | part_iou = np.nan_to_num(np.divide(part_intersect[1:], part_union[1:])) 395 | mean_part_iou = np.mean(part_iou) 396 | buf += 'Category mean IoU: %f\n' % (mean_part_iou) 397 | # Log shape mIoU 398 | buf += 'Shape mean IoU: %f\n' % (shape_iou_tot / float(shape_iou_cnt)) 399 | # Log chamfer distance 400 | try: 401 | mean_dcd = overall_dcd / float(total_seen - exclude_models) 402 | except ZeroDivisionError: 403 | mean_dcd = 1e5 404 | buf += 'Chamfer distance: %f\n' % (mean_dcd) 405 | # Log boundary IoU 406 | boundary_iou /= float(total_seen - exclude_models) 407 | buf += 'Boundary IoU: %f\n' % (boundary_iou) 408 | out_list = ['%3.1f' % (item * 100) for item in part_iou.tolist()] 409 | buf += '%3.1f %3.1f %3.1f %3.1f %3.1f %3.1f %3.1f ;%s\n' % (mean_part_iou * 100, 410 | shape_iou_tot * 100 / float(shape_iou_cnt), mean_dcd * 100, boundary_iou * 100, precision * 100, recall * 100, 411 | f_score * 100, '[' + ', '.join(out_list) + ']') 412 | buf += '=SPLIT("%3.1f,%3.1f,%3.1f,%3.1f,%3.1f,%3.1f,%3.1f", ",")\n' % (mean_part_iou * 100, 413 | shape_iou_tot * 100 / float(shape_iou_cnt), mean_dcd * 100, boundary_iou * 100, precision * 100, recall * 100, 414 | f_score * 100) 415 | 416 | # Log graph cuts evaluation 417 | buf += 'Graph cuts\n----------\n' 418 | buf += PAIRWISE_FEATURES+'\n' 419 | # Log precision 420 | precision_graph_cuts /= float(total_seen - exclude_models) 421 | buf += 'Precision: %f\n' % (precision_graph_cuts) 422 | # Log recall 423 | recall_graph_cuts /= float(total_seen - exclude_models) 424 | buf += 'Recall: %f\n' % (recall_graph_cuts) 425 | # Log f-score 426 | try: 427 | f_score_graph_cuts = 2.0 * (precision_graph_cuts * recall_graph_cuts) / (precision_graph_cuts + recall_graph_cuts) 428 | except ZeroDivisionError: 429 | f_score_graph_cuts = 0.0 430 | buf += 'F-score: %f\n' % (f_score_graph_cuts) 431 | # Log category mIoU 432 | part_iou_graph_cuts = np.nan_to_num(np.divide(part_intersect_graph_cuts[1:], part_union_graph_cuts[1:])) 433 | mean_part_iou_graph_cuts = np.mean(part_iou_graph_cuts) 434 | buf += 'Category mean IoU: %f\n' % (mean_part_iou_graph_cuts) 435 | # Log shape mIoU 436 | buf += 'Shape mean IoU: %f\n' % (shape_iou_tot_graph_cuts / float(shape_iou_cnt_graph_cuts)) 437 | # Log chamfer distance 438 | try: 439 | mean_dcd_graph_cuts = overall_dcd_graph_cuts / float(total_seen - exclude_models) 440 | except ZeroDivisionError: 441 | mean_dcd_graph_cuts = 1e5 442 | buf += 'Chamfer distance: %f\n' % (mean_dcd_graph_cuts) 443 | # Log boundary IoU 444 | boundary_iou_graph_cuts /= float(total_seen - exclude_models) 445 | buf += 'Boundary IoU: %f\n' % (boundary_iou_graph_cuts) 446 | out_list_graph_cuts = ['%3.1f' % (item * 100) for item in part_iou_graph_cuts.tolist()] 447 | buf += '%3.1f %3.1f;%s\n' % (mean_part_iou_graph_cuts * 100, 448 | shape_iou_tot_graph_cuts * 100 / float(shape_iou_cnt_graph_cuts), 449 | '[' + ', '.join(out_list_graph_cuts) + ']') 450 | buf += '%3.1f %3.1f %3.1f %3.1f %3.1f %3.1f %3.1f ;%s\n' % (mean_part_iou_graph_cuts * 100, 451 | shape_iou_tot_graph_cuts * 100 / float(shape_iou_cnt_graph_cuts), mean_dcd_graph_cuts * 100, 452 | boundary_iou_graph_cuts * 100, precision_graph_cuts * 100, recall_graph_cuts * 100, 453 | f_score_graph_cuts * 100, '[' + ', '.join(out_list) + ']') 454 | buf += '=SPLIT("%3.1f,%3.1f,%3.1f,%3.1f,%3.1f,%3.1f,%3.1f", ",")\n' % (mean_part_iou_graph_cuts * 100, 455 | shape_iou_tot_graph_cuts * 100 / float(shape_iou_cnt_graph_cuts), mean_dcd_graph_cuts * 100, 456 | boundary_iou_graph_cuts * 100, precision_graph_cuts * 100, recall_graph_cuts * 100, 457 | f_score_graph_cuts * 100) 458 | 459 | out_fn = os.path.join(EVAL_FOLDER, "evaluation_metrics_" + PAIRWISE_FEATURES + ".txt") 460 | with open(out_fn, 'w') as fout: 461 | fout.write(buf) 462 | 463 | 464 | elapsed_time = time.time() - t1 465 | print("\nFinish evaluation -- time passed {hours:d}:{minutes:d}:{seconds:d}" 466 | .format(hours=int((elapsed_time / 60 ** 2) % (60 ** 2)), minutes=int((elapsed_time / 60) % (60)), 467 | seconds=int(elapsed_time % 60))) -------------------------------------------------------------------------------- /trained_models/README.md: -------------------------------------------------------------------------------- 1 | ### Download trained models 2 | 3 | Trained models will be released soon 4 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils/evaluation_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities used for evaluation 3 | """ 4 | import numpy as np 5 | from scipy import spatial 6 | import rotations3D 7 | 8 | _THRESHOLD_TOL_32 = 2.0 * np.finfo(np.float32).eps 9 | _THRESHOLD_TOL_64 = 2.0 * np.finfo(np.float32).eps 10 | 11 | 12 | def get_pointcloud(dataset, idx, n_features, get_tangent_vectors=False): 13 | """Get i-th pointcloud from specified dataset""" 14 | 15 | # Get data 16 | points, normals, tangent_vectors, seg = dataset[idx] 17 | pointcloud_features = np.hstack((normals, tangent_vectors, seg[:, np.newaxis])) 18 | 19 | # Accumulate input features 20 | input_features = np.zeros((points.shape[0], n_features)) 21 | input_features[:, 0:3] = points 22 | if get_tangent_vectors: 23 | input_features[:, 3:9] = tangent_vectors 24 | 25 | return points, input_features, pointcloud_features 26 | 27 | 28 | def get_pointcloud_evaluation(dataset, idx, n_features, get_tangent_vectors=True): 29 | """Get i-th pointcloud from specified dataset""" 30 | 31 | # Get data 32 | points, normals, tangent_vectors, seg = dataset[idx] 33 | ind = np.where(seg==0)[0] 34 | non_boundary_points = points[ind] 35 | non_boundary_normals = normals[ind] 36 | ind = np.where(seg==1)[0] 37 | boundary_points = points[ind] 38 | boundary_normals = normals[ind] 39 | pointcloud_features = np.hstack((non_boundary_normals, tangent_vectors, seg[seg==0, np.newaxis])) 40 | 41 | # Accumulate input features 42 | input_features = np.zeros((non_boundary_points.shape[0], n_features)) 43 | input_features[:, 0:3] = non_boundary_points 44 | if get_tangent_vectors: 45 | input_features[:, 3:9] = tangent_vectors 46 | 47 | return non_boundary_points, input_features, pointcloud_features, boundary_points, boundary_normals 48 | 49 | 50 | def mIoU_calculation(seg, segp, shape_iou_tot, shape_iou_cnt, part_intersect, part_union, num_semantic_classes): 51 | """Calculate shape and part category mIoU""" 52 | seg_res = segp 53 | seg_res[seg == 0] = 0 54 | cur_pred = seg_res 55 | cur_gt = seg 56 | cur_shape_iou_tot = 0.0 57 | cur_shape_iou_cnt = 0 58 | for j in range(1, num_semantic_classes): 59 | cur_gt_mask = (cur_gt == j) 60 | cur_pred_mask = (cur_pred == j) 61 | 62 | has_gt = (np.sum(cur_gt_mask) > 0) 63 | has_pred = (np.sum(cur_pred_mask) > 0) 64 | 65 | if has_gt or has_pred: 66 | intersect = np.sum(cur_gt_mask & cur_pred_mask) 67 | union = np.sum(cur_gt_mask | cur_pred_mask) 68 | iou = intersect / float(union) 69 | 70 | cur_shape_iou_tot += iou 71 | cur_shape_iou_cnt += 1 72 | 73 | part_intersect[j] += intersect 74 | part_union[j] += union 75 | 76 | if cur_shape_iou_cnt > 0: 77 | cur_shape_miou = cur_shape_iou_tot / float(cur_shape_iou_cnt) 78 | shape_iou_tot += cur_shape_miou 79 | shape_iou_cnt += 1 80 | 81 | return (shape_iou_tot, shape_iou_cnt, part_intersect, part_union) 82 | 83 | 84 | def precision(boundary_points_gt, boundary_points_pred, dist): 85 | """ Defined as the fraction of predicted boundary points in a point cloud that are near any annotated boundary """ 86 | 87 | if len(boundary_points_pred) == 0: 88 | return 0.0, 0, 0 89 | 90 | boundary_points_gt_KDTree = spatial.cKDTree(boundary_points_gt, copy_data=False, balanced_tree=False, compact_nodes=False) 91 | nn_idx = boundary_points_gt_KDTree.query_ball_point(boundary_points_pred, dist) 92 | 93 | TP = 0; FP = 0 94 | for nn in nn_idx: 95 | if len(nn): 96 | TP += 1 97 | else: 98 | FP += 1 99 | assert((TP + FP) == len(boundary_points_pred)) 100 | 101 | try: 102 | P = TP / float(TP+FP) 103 | except ZeroDivisionError: 104 | P = 0.0 105 | 106 | return P, TP, FP 107 | 108 | 109 | def recall(boundary_points_gt, boundary_points_pred, dist): 110 | """ Defined as the fraction of annotated boundary points that are near any predicted boundary point """ 111 | 112 | if len(boundary_points_pred) == 0: 113 | return 0.0, 0, len(boundary_points_gt) 114 | 115 | boundary_points_pred_KDTree = spatial.cKDTree(boundary_points_pred, copy_data=False, balanced_tree=False, compact_nodes=False) 116 | nn_idx = boundary_points_pred_KDTree.query_ball_point(boundary_points_gt, dist) 117 | 118 | TP = 0; FN = 0 119 | for nn in nn_idx: 120 | if len(nn): 121 | TP += 1 122 | else: 123 | FN += 1 124 | assert((TP + FN) == len(boundary_points_gt)) 125 | 126 | try: 127 | R = TP / float(TP+FN) 128 | except ZeroDivisionError: 129 | R = 0.0 130 | 131 | return R, TP, FN 132 | 133 | 134 | def chamfer_distance(**kwargs): 135 | 136 | if len(kwargs.keys()) == 3: 137 | if "segmentation1" in kwargs.keys(): 138 | mandatory_args = ["segmentation2", "points"] 139 | for arg in mandatory_args: 140 | if arg not in kwargs.keys(): 141 | print("ERROR: Argument {arg:s} is missing" .format(arg=arg)) 142 | exit(-1) 143 | return _chamfer_distance_1(segmentation1=kwargs["segmentation1"], segmentation2=kwargs["segmentation2"], 144 | points=kwargs["points"]) 145 | elif "segmentation1_points" in kwargs.keys(): 146 | mandatory_args = ["segmentation2_points", "points"] 147 | for arg in mandatory_args: 148 | if arg not in kwargs.keys(): 149 | print("ERROR: Argument {arg:s} is missing".format(arg=arg)) 150 | exit(-1) 151 | return _chamfer_distance_2(segmentation1_points=kwargs["segmentation1_points"], 152 | segmentation2_points=kwargs["segmentation2_points"], 153 | points=kwargs["points"]) 154 | else: 155 | print("ERROR: Invalid arguments") 156 | exit(-1) 157 | else: 158 | print("ERROR: Invalid nubmer of arguments") 159 | exit(-1) 160 | 161 | 162 | def _chamfer_distance_1(segmentation1, segmentation2, points): 163 | """Calculate chamfer distance between segmentations 1 and 2 and vice versa""" 164 | 165 | directional_mean_dist = [] 166 | for i in range(2): 167 | # Calculate CD for both directions (s1 -> s2, s2 -> s1) 168 | if i == 0: 169 | s1 = segmentation1; s2 = segmentation2 170 | else: 171 | s1 = segmentation2; s2 = segmentation1 172 | 173 | # Find the boundary points of s1 and s2 segmentations 174 | boundary_points_s1 = points[s1 == 1] 175 | boundary_points_s2 = points[s2 == 1] 176 | 177 | # Create kd-tree for boundary points in s2 178 | s2_KD_Tree = spatial.cKDTree(boundary_points_s2, copy_data=False, balanced_tree=False, compact_nodes=False) 179 | 180 | # Find s2 boundary points 1-nn for each point in s1 181 | nn_dist, _ = s2_KD_Tree.query(boundary_points_s1, k=1) 182 | directional_mean_dist.append(np.mean(nn_dist)) 183 | 184 | DCD = (directional_mean_dist[0] + directional_mean_dist[1]) / pc_bounding_box_diagonal(points) 185 | 186 | return DCD 187 | 188 | 189 | def _chamfer_distance_2(segmentation1_points, segmentation2_points, points): 190 | """Calculate chamfer distance between segmentations 1 and 2 and vice versa""" 191 | 192 | directional_mean_dist = [] 193 | for i in range(2): 194 | # Calculate CD for both directions (s1 -> s2, s2 -> s1) 195 | if i == 0: 196 | s1 = segmentation1_points; s2 = segmentation2_points 197 | else: 198 | s1 = segmentation2_points; s2 = segmentation1_points 199 | 200 | # Find the boundary points of s1 and s2 segmentations 201 | boundary_points_s1 = s1 202 | boundary_points_s2 = s2 203 | 204 | # Create kd-tree for boundary points in s2 205 | s2_KD_Tree = spatial.cKDTree(boundary_points_s2, copy_data=False, balanced_tree=False, compact_nodes=False) 206 | 207 | # Find s2 boundary points 1-nn for each point in s1 208 | nn_dist, _ = s2_KD_Tree.query(boundary_points_s1, k=1) 209 | directional_mean_dist.append(np.mean(nn_dist)) 210 | 211 | DCD = (directional_mean_dist[0] + directional_mean_dist[1]) / pc_bounding_box_diagonal(points) 212 | 213 | return DCD 214 | 215 | 216 | def pc_bounding_box_diagonal(pc): 217 | """Calculate point cloud's bounding box""" 218 | centroid = np.mean(pc, axis=0) 219 | pc2 = pc - centroid 220 | 221 | # Calculate bounding box diagonal 222 | xyz_min = np.amin(pc2, axis=0) 223 | xyz_max = np.amax(pc2, axis=0) 224 | bb_diagonal = np.max([np.linalg.norm(xyz_max - xyz_min, ord=2), _THRESHOLD_TOL_64 if pc.dtype==np.float64 else _THRESHOLD_TOL_32]) 225 | 226 | return bb_diagonal 227 | 228 | 229 | def majority(labels): 230 | """Find majority label""" 231 | idx, ctr = 0, 1 232 | for i in range(1, len(labels)): 233 | if labels[i] == labels[idx]: 234 | ctr += 1 235 | else: 236 | ctr -= 1 237 | if ctr == 0: 238 | idx = i 239 | ctr = 1 240 | 241 | return labels[idx], idx, ctr 242 | 243 | 244 | def boundary_annotation(points, semantic_seg): 245 | """Boundary annotate points w.r.t. semantic segmentation""" 246 | 247 | assert(len(points) == len(semantic_seg)) 248 | 249 | # Find maximum sampling distance 250 | points_KD_tree = spatial.cKDTree(points, copy_data=False, balanced_tree=False, compact_nodes=False) 251 | # Find 1-nn distance for all points 252 | nn_dist, _ = points_KD_tree.query(points, k=2) 253 | # Find maximum sampling distance ~ Poisson disk radius 254 | maximum_sampling_distance = np.amax(nn_dist[:, 1]) 255 | 256 | # Annotate a point as a boundary if 1 of its neighbors within a ball radius=maximum_sampling_distance 257 | # has a different semantic label 258 | boundaries = np.zeros_like(semantic_seg) 259 | nn_idx = points_KD_tree.query_ball_point(points, maximum_sampling_distance) 260 | for ind, nn in enumerate(nn_idx): 261 | _, _, ctr = majority(semantic_seg[nn]) 262 | if ctr < len(nn): 263 | boundaries[ind] = 1.0 264 | 265 | return boundaries 266 | 267 | 268 | ### Noise utilities 269 | 270 | 271 | def jitter_points(points, sigma=0.005, clip=0.01, rng=None, return_noise=False): 272 | """ Jitter points """ 273 | assert((points.ndim == 2) or (points.ndim == 3)) 274 | 275 | if points.ndim == 2: 276 | points = np.expand_dims(points, axis=0) 277 | 278 | # Get random noise 279 | if rng is not None: 280 | noise = np.clip(sigma * rng.randn(points.shape[0], points.shape[1], points.shape[2]), -clip, clip) 281 | else: 282 | noise = np.clip(sigma * np.random.randn(points.shape[0], points.shape[1], points.shape[2]), -clip, clip) 283 | 284 | if return_noise: 285 | return np.squeeze(points + noise), np.squeeze(noise) 286 | 287 | return np.squeeze(points + noise) 288 | 289 | 290 | def jitter_direction_vectors(vectors, sigma=1.5, clip=3, rng1=None, rng2=None, return_noise=False): 291 | """ Jitter direction """ 292 | assert ((vectors.ndim == 2) or (vectors.ndim == 3)) 293 | assert (vectors.shape[-1] == 3) 294 | 295 | if vectors.ndim == 2: 296 | vectors = np.expand_dims(vectors, axis=0) 297 | 298 | R_batch = [] 299 | for batch_ind in range(vectors.shape[0]): 300 | # Create random rotation axes 301 | if rng1 is not None: 302 | axes = np.clip(0.5 * rng1.randn(vectors.shape[1], vectors.shape[2]), -1, 1) 303 | else: 304 | axes = np.clip(0.5 * np.random.randn(vectors.shape[1], vectors.shape[2]), -1, 1) 305 | 306 | # Get random angles 307 | if rng2 is not None: 308 | angles = np.clip(sigma * rng2.randn(vectors.shape[1]), -clip, clip) 309 | else: 310 | angles = np.clip(sigma * np.random.randn(vectors.shape[1]), -clip, clip) 311 | 312 | # Get rotation matrices 313 | R = rotations3D.axisAngleMatrixBatch(angle=angles, rotation_axis=axes) 314 | 315 | vectors[batch_ind] = np.squeeze(np.matmul(R, vectors[batch_ind, ..., np.newaxis])) 316 | 317 | if return_noise: 318 | R_batch.append(R) 319 | 320 | if return_noise: 321 | R_batch = np.vstack(R_batch).astype(np.float32) 322 | R_batch = R_batch.reshape(vectors.shape[0], vectors.shape[1], 3, 3) 323 | return np.squeeze(vectors), np.squeeze(R_batch) 324 | 325 | return np.squeeze(vectors) 326 | -------------------------------------------------------------------------------- /utils/local_frames.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | 4 | def construct_local_frames(points, normals, tangent_vec1, tangent_vec2, return_R=False, return_t=False): 5 | """Construct local frame for each point 6 | Input: 7 | points: (batch_size, n_points, 3) TF tensor 8 | normals: (batch_size, n_points, 3) TF tensor 9 | tangent_vec1: (batch_size, n_points, 3) TF tensor 10 | tangent_vec2: (batch_size, n_points, 3) TF tensor 11 | Return: 12 | M: (batch_size, n_points, 3, 4) TF tensor, local frame for each point 13 | """ 14 | 15 | # Build local frame for each point 16 | R = tf.concat([tangent_vec1, tangent_vec2, normals], axis=-1) 17 | R = tf.reshape(R, [points.shape[0].value, points.shape[1].value, 3, 3]) 18 | points = tf.expand_dims(points, axis=3) 19 | t = -tf.matmul(R, points) 20 | M = tf.concat([R, t], axis=3) 21 | 22 | transformations = (M,) 23 | if return_R: 24 | transformations += (R, ) 25 | if return_t: 26 | transformations += (t,) 27 | 28 | return transformations 29 | 30 | 31 | def transform_neighbors(neighbors, M): 32 | """ Transform all neighboring points of a centroid through the transformation M 33 | Input: 34 | neighbors: (batch_size, n_centroids, n_neighbors, 3) TF tensor 35 | M: (batch_size, n_centroids, 3, 4) TF tensor, local frame for each centroid 36 | Return: 37 | transformed_neighbors = (batch_size, n_centroids, n_neighbors, 3) 38 | """ 39 | 40 | # Sanity check 41 | B = neighbors.shape[0]; C = neighbors.shape[1]; N = neighbors.shape[2]; D = neighbors.shape[3] 42 | assert (M.shape[0] == B); assert (M.shape[1] == C); assert (M.shape[2] == D); 43 | assert (M.shape[3] == 4); 44 | 45 | # Project all neighbors 46 | ones = tf.ones((neighbors.shape[0].value, neighbors.shape[1].value, neighbors.shape[2].value, 1), dtype=tf.float32) 47 | h_neighbors = tf.concat([neighbors, ones], axis=3) 48 | h_neighbors = tf.transpose(h_neighbors, [0,1,3,2]) 49 | projected_neighbors = tf.matmul(M, h_neighbors) 50 | projected_neighbors = tf.transpose(projected_neighbors, [0,1,3,2]) 51 | 52 | return projected_neighbors 53 | 54 | 55 | def rotate_vectors(vectors, R): 56 | """ Transform vectors using rotation matrix R 57 | Input: 58 | vectors: (batch_size, n_centroids, n_neighbors, 3) TF tensor 59 | R: (batch_size, n_points, 3, 3) TF tensor, rotation matrix 60 | Return: 61 | rotated_vectors = (batch_size, n_centroids, n_neighbors, 3) 62 | """ 63 | 64 | # Rotate vectors 65 | rotated_vectors = tf.matmul(R, tf.transpose(vectors, [0,1,3,2])) 66 | 67 | return tf.transpose(rotated_vectors, [0,1,3,2]) 68 | 69 | 70 | def project_neighbors_to_local_frame(centroids, neighbors, normals, tangent_vec1, tangent_vec2): 71 | """Project each neighbor of the centroid to its local frame 72 | Input: 73 | centroids: (batch_size, n_centroids, 3) TF tensor 74 | neighbors: (batch_size, n_centroids, n_neighbors, 3) TF tensor 75 | normals: (batch_size, n_centroids, 3) TF tensor 76 | tangent_vec1: (batch_size, n_centroids, 3) TF tensor 77 | tangent_vec2: (batch_size, n_centroids, 3) TF tensor 78 | Return: 79 | projected_neighbors: (batch_size, n_centroids, n_neighbors, 3) TF tensor, local coordinates of each 80 | neighbor sample w.r.t. the local frame of their centroid 81 | """ 82 | 83 | # Sanity check 84 | B = centroids.shape[0]; C = centroids.shape[1] 85 | assert (neighbors.shape[0] == B); assert (neighbors.shape[1] == C) 86 | 87 | # Get local frame for each centroid 88 | M = construct_local_frames(points=centroids, normals=normals, tangent_vec1=tangent_vec1, 89 | tangent_vec2=tangent_vec2)[0] 90 | 91 | # Project all neighbors 92 | projected_neighbors = transform_neighbors(neighbors=neighbors, M=M) 93 | 94 | return projected_neighbors -------------------------------------------------------------------------------- /utils/provider.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | import h5py 5 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 6 | sys.path.append(BASE_DIR) 7 | 8 | def shuffle_data(data, labels): 9 | """ Shuffle data and labels. 10 | Input: 11 | data: B,N,... numpy array 12 | label: B,... numpy array 13 | Return: 14 | shuffled data, label and shuffle indices 15 | """ 16 | idx = np.arange(len(labels)) 17 | np.random.shuffle(idx) 18 | return data[idx, ...], labels[idx], idx 19 | 20 | def shuffle_points(batch_data): 21 | """ Shuffle orders of points in each point cloud -- changes FPS behavior. 22 | Use the same shuffling idx for the entire batch. 23 | Input: 24 | BxNxC array 25 | Output: 26 | BxNxC array 27 | """ 28 | idx = np.arange(batch_data.shape[1]) 29 | np.random.shuffle(idx) 30 | return batch_data[:,idx,:] 31 | 32 | def rotate_point_cloud(batch_data): 33 | """ Randomly rotate the point clouds to augument the dataset 34 | rotation is per shape based along up direction 35 | Input: 36 | BxNx3 array, original batch of point clouds 37 | Return: 38 | BxNx3 array, rotated batch of point clouds 39 | """ 40 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 41 | for k in xrange(batch_data.shape[0]): 42 | rotation_angle = np.random.uniform() * 2 * np.pi 43 | cosval = np.cos(rotation_angle) 44 | sinval = np.sin(rotation_angle) 45 | rotation_matrix = np.array([[cosval, 0, sinval], 46 | [0, 1, 0], 47 | [-sinval, 0, cosval]]) 48 | shape_pc = batch_data[k, ...] 49 | rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 50 | return rotated_data 51 | 52 | 53 | def rotate_point_cloud_with_normal(batch_xyz_normal): 54 | ''' Randomly rotate XYZ, normal point cloud. 55 | Input: 56 | batch_xyz_normal: B,N,6, first three channels are XYZ, last 3 all normal 57 | Output: 58 | B,N,6, rotated XYZ, normal point cloud 59 | ''' 60 | for k in xrange(batch_xyz_normal.shape[0]): 61 | rotation_angle = np.random.uniform() * 2 * np.pi 62 | cosval = np.cos(rotation_angle) 63 | sinval = np.sin(rotation_angle) 64 | rotation_matrix = np.array([[cosval, 0, sinval], 65 | [0, 1, 0], 66 | [-sinval, 0, cosval]]) 67 | shape_pc = batch_xyz_normal[k,:,0:3] 68 | shape_normal = batch_xyz_normal[k,:,3:6] 69 | batch_xyz_normal[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 70 | batch_xyz_normal[k,:,3:6] = np.dot(shape_normal.reshape((-1, 3)), rotation_matrix) 71 | return batch_xyz_normal 72 | 73 | def rotate_perturbation_point_cloud_with_normal(batch_data, angle_sigma=0.06, angle_clip=0.18): 74 | """ Randomly perturb the point clouds by small rotations 75 | Input: 76 | BxNx6 array, original batch of point clouds and point normals 77 | Return: 78 | BxNx3 array, rotated batch of point clouds 79 | """ 80 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 81 | for k in xrange(batch_data.shape[0]): 82 | angles = np.clip(angle_sigma*np.random.randn(3), -angle_clip, angle_clip) 83 | Rx = np.array([[1,0,0], 84 | [0,np.cos(angles[0]),-np.sin(angles[0])], 85 | [0,np.sin(angles[0]),np.cos(angles[0])]]) 86 | Ry = np.array([[np.cos(angles[1]),0,np.sin(angles[1])], 87 | [0,1,0], 88 | [-np.sin(angles[1]),0,np.cos(angles[1])]]) 89 | Rz = np.array([[np.cos(angles[2]),-np.sin(angles[2]),0], 90 | [np.sin(angles[2]),np.cos(angles[2]),0], 91 | [0,0,1]]) 92 | R = np.dot(Rz, np.dot(Ry,Rx)) 93 | shape_pc = batch_data[k,:,0:3] 94 | shape_normal = batch_data[k,:,3:6] 95 | rotated_data[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), R) 96 | rotated_data[k,:,3:6] = np.dot(shape_normal.reshape((-1, 3)), R) 97 | return rotated_data 98 | 99 | 100 | def rotate_point_cloud_by_angle(batch_data, rotation_angle): 101 | """ Rotate the point cloud along up direction with certain angle. 102 | Input: 103 | BxNx3 array, original batch of point clouds 104 | Return: 105 | BxNx3 array, rotated batch of point clouds 106 | """ 107 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 108 | for k in xrange(batch_data.shape[0]): 109 | #rotation_angle = np.random.uniform() * 2 * np.pi 110 | cosval = np.cos(rotation_angle) 111 | sinval = np.sin(rotation_angle) 112 | rotation_matrix = np.array([[cosval, 0, sinval], 113 | [0, 1, 0], 114 | [-sinval, 0, cosval]]) 115 | shape_pc = batch_data[k,:,0:3] 116 | rotated_data[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 117 | return rotated_data 118 | 119 | def rotate_point_cloud_by_angle_with_normal(batch_data, rotation_angle): 120 | """ Rotate the point cloud along up direction with certain angle. 121 | Input: 122 | BxNx3 array, original batch of point clouds 123 | Return: 124 | BxNx3 array, rotated batch of point clouds 125 | """ 126 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 127 | for k in xrange(batch_data.shape[0]): 128 | #rotation_angle = np.random.uniform() * 2 * np.pi 129 | cosval = np.cos(rotation_angle) 130 | sinval = np.sin(rotation_angle) 131 | rotation_matrix = np.array([[cosval, 0, sinval], 132 | [0, 1, 0], 133 | [-sinval, 0, cosval]]) 134 | shape_pc = batch_data[k, ...] 135 | shape_normal = batch_data[k,:,3:6] 136 | rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 137 | rotated_data[k,:,3:6] = np.dot(shape_normal.reshape((-1,3)), rotation_matrix) 138 | return rotated_data 139 | 140 | 141 | 142 | def rotate_perturbation_point_cloud(batch_data, angle_sigma=0.06, angle_clip=0.18): 143 | """ Randomly perturb the point clouds by small rotations 144 | Input: 145 | BxNx3 array, original batch of point clouds 146 | Return: 147 | BxNx3 array, rotated batch of point clouds 148 | """ 149 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 150 | for k in xrange(batch_data.shape[0]): 151 | angles = np.clip(angle_sigma*np.random.randn(3), -angle_clip, angle_clip) 152 | Rx = np.array([[1,0,0], 153 | [0,np.cos(angles[0]),-np.sin(angles[0])], 154 | [0,np.sin(angles[0]),np.cos(angles[0])]]) 155 | Ry = np.array([[np.cos(angles[1]),0,np.sin(angles[1])], 156 | [0,1,0], 157 | [-np.sin(angles[1]),0,np.cos(angles[1])]]) 158 | Rz = np.array([[np.cos(angles[2]),-np.sin(angles[2]),0], 159 | [np.sin(angles[2]),np.cos(angles[2]),0], 160 | [0,0,1]]) 161 | R = np.dot(Rz, np.dot(Ry,Rx)) 162 | shape_pc = batch_data[k, ...] 163 | rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), R) 164 | return rotated_data 165 | 166 | 167 | def jitter_point_cloud(batch_data, sigma=0.01, clip=0.05): 168 | """ Randomly jitter points. jittering is per point. 169 | Input: 170 | BxNx3 array, original batch of point clouds 171 | Return: 172 | BxNx3 array, jittered batch of point clouds 173 | """ 174 | B, N, C = batch_data.shape 175 | assert(clip > 0) 176 | jittered_data = np.clip(sigma * np.random.randn(B, N, C), -1*clip, clip) 177 | jittered_data += batch_data 178 | return jittered_data 179 | 180 | def shift_point_cloud(batch_data, shift_range=0.1): 181 | """ Randomly shift point cloud. Shift is per point cloud. 182 | Input: 183 | BxNx3 array, original batch of point clouds 184 | Return: 185 | BxNx3 array, shifted batch of point clouds 186 | """ 187 | B, N, C = batch_data.shape 188 | shifts = np.random.uniform(-shift_range, shift_range, (B,3)) 189 | for batch_index in range(B): 190 | batch_data[batch_index,:,:] += shifts[batch_index,:] 191 | return batch_data 192 | 193 | 194 | def random_scale_point_cloud(batch_data, scale_low=0.8, scale_high=1.25): 195 | """ Randomly scale the point cloud. Scale is per point cloud. 196 | Input: 197 | BxNx3 array, original batch of point clouds 198 | Return: 199 | BxNx3 array, scaled batch of point clouds 200 | """ 201 | B, N, C = batch_data.shape 202 | scales = np.random.uniform(scale_low, scale_high, B) 203 | for batch_index in range(B): 204 | batch_data[batch_index,:,:] *= scales[batch_index] 205 | return batch_data 206 | 207 | def random_point_dropout(batch_pc, max_dropout_ratio=0.875): 208 | ''' batch_pc: BxNx3 ''' 209 | for b in range(batch_pc.shape[0]): 210 | dropout_ratio = np.random.random()*max_dropout_ratio # 0~0.875 211 | drop_idx = np.where(np.random.random((batch_pc.shape[1]))<=dropout_ratio)[0] 212 | if len(drop_idx)>0: 213 | batch_pc[b,drop_idx,:] = batch_pc[b,0,:] # set to the first point 214 | return batch_pc 215 | 216 | 217 | def getDataFiles(list_filename): 218 | return [line.rstrip() for line in open(list_filename)] 219 | 220 | def load_h5(h5_filename): 221 | f = h5py.File(h5_filename) 222 | data = f['data'][:] 223 | label = f['label'][:] 224 | return (data, label) 225 | 226 | def loadDataFile(filename): 227 | return load_h5(filename) 228 | -------------------------------------------------------------------------------- /utils/rotations3D.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | _THRESHOLD_TOL_32 = 2.0 * np.finfo(np.float32).eps 4 | _THRESHOLD_TOL_64 = 2.0 * np.finfo(np.float32).eps 5 | 6 | def eulerAnglesMatrix(thetax=0.0, thetay=0.0, thetaz=0.0): 7 | """ 3D rotation matrix based on euler angles """ 8 | 9 | return np.matmul(np.matmul(rotMatrixY(thetay), rotMatrixZ(thetaz)), rotMatrixX(thetax)) 10 | 11 | 12 | def rotMatrixX(thetax = 0.0): 13 | """ 3D rotation matrix around x-axis """ 14 | thetax = np.radians(thetax) 15 | cosx, sinx = np.cos(thetax), np.sin(thetax) 16 | 17 | return np.array(((1.0, 0.0, 0.0), (0.0, cosx, -sinx), (0.0, sinx, cosx))) 18 | 19 | 20 | def rotMatrixY(thetay = 0.0): 21 | """ 3D rotation matrix around y-axis """ 22 | thetay = np.radians(thetay) 23 | cosy, siny = np.cos(thetay), np.sin(thetay) 24 | 25 | return np.array(((cosy, 0.0, siny), (0.0, 1.0, 0.0), (-siny, 0.0, cosy))) 26 | 27 | 28 | def rotMatrixZ(thetaz = 0.0): 29 | """ 3D rotation matrix around z-axis """ 30 | thetaz = np.radians(thetaz) 31 | cosz, sinz = np.cos(thetaz), np.sin(thetaz) 32 | 33 | return np.array(((cosz, -sinz, 0.0), (sinz, cosz, 0.0), (0.0, 0.0, 1.0))) 34 | 35 | 36 | 37 | def axisAngleMatrix(angle=0.0, axis=np.array([1, 0, 0]), angleType='deg'): 38 | """ 3D roation matrix based on axis-angle representation """ 39 | 40 | assert(axis.shape == (3,)) 41 | assert(angleType == 'deg' or angleType == 'rad') 42 | 43 | # Normalize axis 44 | axis = axis / np.linalg.norm(axis) 45 | 46 | # Use Rodrigues' rotation formula to form a 3D rotation matrix 47 | if angleType == 'deg': 48 | angle = np.radians(angle) 49 | K = skewSymmetric(axis) 50 | R = np.eye(3, dtype=np.float32) + np.sin(angle)*K + (1-np.cos(angle))*np.matmul(K, K) 51 | 52 | return R 53 | 54 | 55 | def axisAngleMatrixBatch(angle, rotation_axis, angleType='deg'): 56 | """ 3D roation matrix based on axis-angle representation """ 57 | 58 | assert(angleType == 'deg' or angleType == 'rad') 59 | 60 | # Normalize axis 61 | axis_norm = np.linalg.norm(rotation_axis, axis=1, keepdims=True) 62 | if axis_norm.dtype == np.float32: 63 | axis_norm[axis_norm < _THRESHOLD_TOL_32] = _THRESHOLD_TOL_32 64 | elif axis_norm.dtype == np.float64: 65 | axis_norm[axis_norm < _THRESHOLD_TOL_64] = _THRESHOLD_TOL_64 66 | rotation_axis = rotation_axis / axis_norm 67 | 68 | # Use Rodrigues' rotation formula to form a 3D rotation matrix 69 | if angleType == 'deg': 70 | angle = np.radians(angle) 71 | K = skewSymmetricBatch(rotation_axis) 72 | eye = np.eye(3, dtype=np.float32) 73 | R = np.tile(eye, (rotation_axis.shape[0], 1, 1)) + np.sin(angle[:, np.newaxis, np.newaxis])*K \ 74 | + (1-np.cos(angle[:, np.newaxis, np.newaxis]))*np.linalg.matrix_power(K, 2) 75 | 76 | return R 77 | 78 | 79 | def skewSymmetric(x=np.array([1, 1, 1])): 80 | """ Return a skew symmetric matrix (A == -A.T) of a 3d vector """ 81 | 82 | assert(x.shape==(3,)) 83 | 84 | return np.array([[0.0, -x[2], x[1]], 85 | [x[2], 0.0, -x[0]], 86 | [-x[1], x[0], 0.0]], dtype=np.float32) 87 | 88 | 89 | def skewSymmetricBatch(x): 90 | """ Return a skew symmetric matrix (A == -A.T) of a 3d vector """ 91 | 92 | K = np.zeros((x.shape[0], 3, 3), dtype=np.float32) 93 | K[:,0,1] = -x[:,2]; K[:,0,2] = x[:,1]; K[:,1,0] = x[:,2]; K[:,1,2] = -x[:,0]; K[:,2,0] = -x[:,1]; K[:,2,1] = x[:,0] 94 | 95 | return K 96 | --------------------------------------------------------------------------------