--- a
+++ b/mylib/utils/misc.py
@@ -0,0 +1,192 @@
+import collections
+from itertools import repeat
+import numpy as np
+import scipy
+import matplotlib.pyplot as plt
+from skimage.measure import find_contours
+
+
+def plot_voxel(arr, aux=None):
+    if aux is not None:
+        assert arr.shape == aux.shape
+    length = arr.shape[0]
+    _, axes = plt.subplots(length, 1, figsize=(4, 4 * length))
+    for i, ax in enumerate(axes):
+        ax.set_title("@%s" % i)
+        ax.imshow(arr[i], cmap=plt.cm.gray)
+        if aux is not None:
+            ax.imshow(aux[i], alpha=0.3)
+    plt.show()
+
+
+def plot_voxel_save(path, arr, aux=None):
+    if aux is not None:
+        assert arr.shape == aux.shape
+    length = arr.shape[0]
+    for i in range(length):
+        plt.clf()
+        plt.title("@%s" % i)
+        plt.imshow(arr[i], cmap=plt.cm.gray)
+        if aux is not None:
+            plt.imshow(aux[i], alpha=0.2)
+        plt.savefig(path + "%s.png" % i)
+
+
+def plot_voxel_enhance(arr, arr_mask=None, figsize=10, alpha=0.1):  # zyx
+    '''borrow from yuxiang.'''
+    plt.figure(figsize=(figsize, figsize))
+    rows = cols = int(round(np.sqrt(arr.shape[0])))
+    img_height = arr.shape[1]
+    img_width = arr.shape[2]
+    assert img_width == img_height
+    res_img = np.zeros((rows * img_height, cols * img_width), dtype=np.uint8)
+    if arr_mask is not None:
+        res_mask_img = np.zeros(
+            (rows * img_height, cols * img_width), dtype=np.uint8)
+    for row in range(rows):
+        for col in range(cols):
+            if (row * cols + col) >= arr.shape[0]:
+                continue
+            target_y = row * img_height
+            target_x = col * img_width
+            res_img[target_y:target_y + img_height,
+            target_x:target_x + img_width] = arr[row * cols + col]
+            if arr_mask is not None:
+                res_mask_img[target_y:target_y + img_height,
+                target_x:target_x + img_width] = arr_mask[row * cols + col]
+    plt.imshow(res_img, plt.cm.gray)
+    if arr_mask is not None:
+        plt.imshow(res_mask_img, alpha=alpha)
+    plt.show()
+
+
+def find_edges(mask, level=0.5):
+    edges = find_contours(mask, level)[0]
+    ys = edges[:, 0]
+    xs = edges[:, 1]
+    return xs, ys
+
+
+def plot_contours(arr, aux, level=0.5, ax=None, **kwargs):
+    if ax is None:
+        _, ax = plt.subplots(1, 1, **kwargs)
+    ax.imshow(arr, cmap=plt.cm.gray)
+    xs, ys = find_edges(aux, level)
+    ax.plot(xs, ys)
+
+
+def crop_at_zyx_with_dhw(voxel, zyx, dhw, fill_with):
+    '''Crop and pad on the fly.'''
+    shape = voxel.shape
+    # z, y, x = zyx
+    # d, h, w = dhw
+    crop_pos = []
+    padding = [[0, 0], [0, 0], [0, 0]]
+    for i, (center, length) in enumerate(zip(zyx, dhw)):
+        assert length % 2 == 0
+        # assert center < shape[i] # it's not necessary for "moved center"
+        low = round(center) - length // 2
+        high = round(center) + length // 2
+        if low < 0:
+            padding[i][0] = int(0 - low)
+            low = 0
+        if high > shape[i]:
+            padding[i][1] = int(high - shape[i])
+            high = shape[i]
+        crop_pos.append([int(low), int(high)])
+    cropped = voxel[crop_pos[0][0]:crop_pos[0][1], crop_pos[1]
+                                                   [0]:crop_pos[1][1], crop_pos[2][0]:crop_pos[2][1]]
+    if np.sum(padding) > 0:
+        cropped = np.lib.pad(cropped, padding, 'constant',
+                             constant_values=fill_with)
+    return cropped
+
+
+def window_clip(v, window_low=-1024, window_high=400, dtype=np.uint8):
+    '''Use lung windown to map CT voxel to grey.'''
+    # assert v.min() <= window_low
+    return np.round(np.clip((v - window_low) / (window_high - window_low) * 255., 0, 255)).astype(dtype)
+
+
+def resize(voxel, spacing, new_spacing=[1., 1., 1.]):
+    '''Resize `voxel` from `spacing` to `new_spacing`.'''
+    resize_factor = []
+    for sp, nsp in zip(spacing, new_spacing):
+        resize_factor.append(float(sp) / nsp)
+    resized = scipy.ndimage.interpolation.zoom(voxel, resize_factor, mode='nearest')
+    for i, (sp, shape, rshape) in enumerate(zip(spacing, voxel.shape, resized.shape)):
+        new_spacing[i] = float(sp) * shape / rshape
+    return resized, new_spacing
+
+
+def rotation(array, angle):
+    '''using Euler angles method.
+    @author: renchao
+    @params:
+        angle: 0: no rotation, 1: rotate 90 deg, 2: rotate 180 deg, 3: rotate 270 deg
+    '''
+    #
+    X = np.rot90(array, angle[0], axes=(0, 1))  # rotate in X-axis
+    Y = np.rot90(X, angle[1], axes=(0, 2))  # rotate in Y'-axis
+    Z = np.rot90(Y, angle[2], axes=(1, 2))  # rotate in Z"-axis
+    return Z
+
+
+def reflection(array, axis):
+    '''
+    @author: renchao
+    @params:
+        axis: -1: no flip, 0: Z-axis, 1: Y-axis, 2: X-axis
+    '''
+    if axis != -1:
+        ref = np.flip(array, axis)
+    else:
+        ref = np.copy(array)
+    return ref
+
+
+def crop(array, zyx, dhw):
+    z, y, x = zyx
+    d, h, w = dhw
+    cropped = array[z - d // 2:z + d // 2,
+              y - h // 2:y + h // 2,
+              x - w // 2:x + w // 2]
+    return cropped
+
+
+def random_center(shape, move):
+    offset = np.random.randint(-move, move + 1, size=3)
+    zyx = np.array(shape) // 2 + offset
+    return zyx
+
+
+def get_uniform_assign(length, subset):
+    assert subset > 0
+    per_length, remain = divmod(length, subset)
+    total_set = np.random.permutation(list(range(subset)) * per_length)
+    remain_set = np.random.permutation(list(range(subset)))[:remain]
+    return list(total_set) + list(remain_set)
+
+
+def split_validation(df, subset, by):
+    df = df.copy()
+    for sset in df[by].unique():
+        length = (df[by] == sset).sum()
+        df.loc[df[by] == sset, 'subset'] = get_uniform_assign(length, subset)
+    df['subset'] = df['subset'].astype(int)
+    return df
+
+
+def _ntuple(n):
+    def parse(x):
+        if isinstance(x, collections.Iterable):
+            return x
+        return tuple(repeat(x, n))
+
+    return parse
+
+
+_single = _ntuple(1)
+_pair = _ntuple(2)
+_triple = _ntuple(3)
+_quadruple = _ntuple(4)