diff --git a/HISTORY.rst b/HISTORY.rst index 19dd955..c200a92 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,11 @@ History ======= +0.3.6 (2022-01-13) +------------------- +* Include Open3d library for point downsampling +* Maintenance update + 0.3.5 (2022-01-03) ------------------- * HotFix for folder selection on MacOS diff --git a/requirements.txt b/requirements.txt index 0d27d25..ea2fa16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ +click>=8.0.3 setuptools>=57.1.0 -click>=8.0.1 -scikit-image>=0.18.2 -numpy>=1.21.0 +scikit-image>=0.19.1 +numpy>=1.20.0 tifffile>=2021.7.2 -tqdm>=4.61.1 -imagecodecs>=2021.6.8 -opencv-python>=4.5.0 -scipy>=1.7.0 +tqdm>=4.62.3 +imagecodecs>=2021.8.26 +opencv-python>=4.5.4.60 +scipy>=1.7.3 open3d>=0.14.1 \ No newline at end of file diff --git a/setup.py b/setup.py index d707893..5179bf7 100644 --- a/setup.py +++ b/setup.py @@ -8,16 +8,16 @@ with open('HISTORY.rst') as history_file: history = history_file.read() -requirements = ['click>=8.0.1', +requirements = ['click>=8.0.3', 'setuptools>=57.1.0', - 'scikit-image>=0.18.2', - 'numpy>=1.21.0', + 'scikit-image>=0.19.1', + 'numpy>=1.20.0', 'tifffile>=2021.7.2', - 'tqdm>=4.61.1', - 'imagecodecs>=2021.6.8', - 'edt>=2.1.1', - 'opencv-python>=4.5.0', - 'scipy>=1.7.0'] + 'tqdm>=4.62.3', + 'imagecodecs>=2021.8.26', + 'opencv-python>=4.5.4.60', + 'scipy>=1.7.3', + 'open3d>=0.14.1'] setup(author="Robert Kiewisz", author_email='robert.kiewisz@gmail.com', diff --git a/slcpy/build_graph.py b/slcpy/build_graph.py index 68413c8..6ee360e 100644 --- a/slcpy/build_graph.py +++ b/slcpy/build_graph.py @@ -1,9 +1,7 @@ -from os import listdir, getcwd +from os import getcwd, listdir from os.path import join -from typing import Optional import click -from click.core import Option import numpy as np from tifffile import tifffile from tqdm import tqdm @@ -25,10 +23,6 @@ default=6, help='Filter size matrix for denoising.', show_default=True) -@click.option('-c', '--clean_graph', - default=10, - help='Clean graph from neighborhood points.', - show_default=True) @click.option('-d', '--down_sampling', default=0, help='Down-sample point cloud by the factor of...', @@ -42,41 +36,60 @@ def main(dir_path: str, output: str, filter: int, - clean_graph: int, save: str, - down_sampling: Optional[int]=None): + down_sampling: bool): """ MAIN MODULE FOR EXTRACTING POINT CLOUD FROM SEMANTIC LABEL Args: - -dir / dir_path: Directory to the folder with image dataset. - -o / output: Output directory for saving transformed files. - -f / filter: filter size matrix for denoising - -c /clean_graph: Clean graph from neighborhood points. - -s / save: select type of saved data other numpy .npy or .csv + dir_path: Directory to the folder with image dataset. + output: Output directory for saving transformed files. + filter: filter size matrix for denoising + clean_graph: Clean graph from neighborhood points. + save: select type of saved data other numpy .npy or .csv """ for file in tqdm(listdir(dir_path)): if file.endswith('.tif'): - img, coords = slcpy_graph(dir_path=join(dir_path, file), - filter_img=filter, - down_sampling=down_sampling) + if down_sampling: + img, coords_HD, coords_LD = slcpy_graph(dir_path=join(dir_path, file), + filter_img=filter, + down_sampling=down_sampling) + else: + img, coords_HD = slcpy_graph(dir_path=join(dir_path, file), + filter_img=filter, + down_sampling=down_sampling) + if save == "numpy": - np.save(join(output, file[:-4]), - coords) + np.save(join(output, file[:-4] + '_HD'), + coords_HD) + if down_sampling: + np.save(join(output, file[:-4] + '_LD'), + coords_LD) elif save == "csv": - np.savetxt(join(output, str(file[:-4] + ".csv")), - coords, + np.savetxt(join(output, str(file[:-4] + '_HD' + ".csv")), + coords_HD, delimiter=",") + if down_sampling: + np.savetxt(join(output, str(file[:-4] + '_LD' + ".csv")), + coords_LD, + delimiter=",") elif save == "all": tifffile.imwrite(join(output, file[:-4] + '_denoise.tif'), np.array(img, 'int8')) np.save(join(output, file[:-4]), - coords) + coords_HD) np.savetxt(join(output, str(file[:-4] + ".csv")), - coords, + coords_HD, delimiter=",") + if down_sampling: + np.save(join(output, file[:-4] + '_LD'), + coords_LD) + np.savetxt(join(output, str(file[:-4] + + '_LD' + ".csv")), + coords_LD, + delimiter=",") + if __name__ == '__main__': main() diff --git a/slcpy/build_semantic.py b/slcpy/build_semantic.py index 97d7bdd..ec2a720 100644 --- a/slcpy/build_semantic.py +++ b/slcpy/build_semantic.py @@ -1,7 +1,6 @@ -from os import mkdir, rename, listdir, getcwd +from os import getcwd, listdir, mkdir, rename from os.path import isdir, join from shutil import rmtree -from time import sleep import click import numpy as np @@ -52,7 +51,7 @@ default=None, help='Define size in pixels of output images in z.', show_default=True) -@click.option('-a', '--filter_empty_patches', +@click.option('-f', '--filter_empty_patches', default=False, help='If True only images containing any data are saved.', show_default=True) @@ -75,22 +74,22 @@ def main(dir_path, MAIN MODULE FOR COMPOSING SEMANTIC LABEL FROM GIVEN POINT CLOUD Args: - -dir / dir_path: Directory to the folder with image dataset. - -o / output: Output directory for saving transformed files. - -m / build_mask: Define if the semantic mask should be build and saved. - -px / pixel_size: Pixel size for all images. Note that if images has + dir_path: Directory to the folder with image dataset. + output: Output directory for saving transformed files. + build_mask: Define if the semantic mask should be build and saved. + pixel_size: Pixel size for all images. Note that if images has different pixel size set to None to automatically calculate it for each image. - -d / circle_size: Size of drawn circle in Angstrom. - -l / multi_classification: If True as an output each line is drawn + circle_size: Size of drawn circle in Angstrom. + multi_classification: If True as an output each line is drawn with unique label. - -t / pretrim_mask: If True the image mask will be trimmed before + pretrim_mask: If True the image mask will be trimmed before building label mask. It's helpful for big files to speed up computation. - -xy / trim_size_xy: Final XY dimension of output images. - -z / time_size_z: Final Z dimension of output images. - -a / filter_empty_patches: Use whole image for trimming - -s / stride: stride for patch step size with overlay + trim_size_xy: Final XY dimension of output images. + time_size_z: Final Z dimension of output images. + filter_empty_patches: Use whole image for trimming + stride: stride for patch step size with overlay """ if isdir(output): @@ -126,7 +125,7 @@ def main(dir_path, for file in batch_iter: img_name = file - mask_name = file[:-4] + r'_mask.tif' + mask_name = file[:-4] + '_mask.tif' image_counter += 1 if file.endswith('.tif'): @@ -153,7 +152,7 @@ def main(dir_path, np.array(label_mask, 'int8')) else: if filter_empty_patches: - idx = trim_images(image=image, + idx = trim_images(image=image, label_mask=label_mask, trim_size_xy=trim_size_xy, trim_size_z=trim_size_z, @@ -161,7 +160,7 @@ def main(dir_path, output=output, image_counter=idx) else: - idx = trim_to_patches(image=image, + idx = trim_to_patches(image=image, label_mask=label_mask, trim_size_xy=trim_size_xy, trim_size_z=trim_size_z, diff --git a/slcpy/main.py b/slcpy/main.py index fb55d93..d57d842 100644 --- a/slcpy/main.py +++ b/slcpy/main.py @@ -30,6 +30,7 @@ def trim_label_mask(points: np.ndarray, image_trim = image[int(min_z):int(max_z), int(min_y):int(max_y), int(min_x):int(max_x)] + label_mask_trim = label_mask[int(min_z):int(max_z), int(min_y):int(max_y), int(min_x):int(max_x)] @@ -44,19 +45,19 @@ def trim_label_mask(points: np.ndarray, def slcpy_semantic(dir_path: str, mask: bool, pixel_size=None, - circle_size=125, + circle_size=250, multi_layer=False, - trim_mask=True): + trim_mask=False): """ MODULE TO LOAD 3D .tif FILES WITH CORRESPONDING .am FILES Args: dir_path: path direction of the input file *.tif - mask: If True + mask: If True semantic mask is created pixel_size: pixel size in Angstrom circle_size: size of a circle the label mask in Angstrom - trim_mask: True/False statement for trimming input data multi_layer: single, or unique value for each lines + trim_mask: If True input data are trimed to the point cloud """ print(" Converting image {}".format(dir_path)) img = ImportDataFromAmira( @@ -136,15 +137,14 @@ def slcpy_stitch(dir_path: str, def slcpy_graph(dir_path: str or np.ndarray, filter_img: int, - down_sampling: int): + down_sampling: bool): """ MODULE TO BUILD POINT CLOUD FROM 3D .tiff FILES Args: dir_path: Path direction of the input file *.tif with semantic masks. - filter_img: Filter size for cleaning lonely pixels. - clean_graph: Min number of pixels between each coord can be picked - down_sampling: Number of downsample iterations. + filter_img: Remove object that are smaller then given pixel size. + down_sampling: If True downsampling is perform on build point cloud. """ img = ImportSemanticMask(src_tiff=dir_path) diff --git a/slcpy/stitch_images.py b/slcpy/stitch_images.py index c33efcb..0a084f5 100644 --- a/slcpy/stitch_images.py +++ b/slcpy/stitch_images.py @@ -45,12 +45,12 @@ def main(dir_path: str, Main module for stitch individual images into montaged image Args: - -dir / dir_path: Directory to the folder with image dataset. - -o / output: Output directory for saving transformed files. - -m / mask: Indicate if stitched images are mask or images. - -pf / prefix: if not None, indicate additional file prefix. - -b / binary: If True transform date to binary format. - -dt /dtype: Data format type. + dir_path: Directory to the folder with image dataset. + output: Output directory for saving transformed files. + mask: Indicate if stitched images are mask or images. + prefix: if not None, indicate additional file prefix. + binary: If True transform date to binary format. + dtype: Data format type. """ stitched_image = slcpy_stitch(dir_path, diff --git a/slcpy/utils/import_data.py b/slcpy/utils/import_data.py index e1666bb..3b5f828 100644 --- a/slcpy/utils/import_data.py +++ b/slcpy/utils/import_data.py @@ -13,11 +13,12 @@ class ImportDataFromAmira: """ - Class module to load 3D .tif file + MAIN CLASS TO HANDLE 3D .TIF AND .AM DATA Args: src_tiff: source of the 3D .tif file src_am: source of the spatial graph for corresponding 3D .tif + mask: If True output semnantic mask pixel_size: numeric value of pixel size """ @@ -205,13 +206,12 @@ def get_points(self): class ImportSemanticMask: """ - Class module to load 3D .tif file + MAIN MODULE TO LOAD SEMANTIC MASK Args: src_tiff: source of the 3D .tif file filter_small_object: Filter size to remove small object . clean_close_point: If True, close point will be removed. - """ def __init__(self, @@ -261,12 +261,12 @@ def find_maximas(self, x, y, z = [], [], [] sk_image = skeletonize_3d(denoise_img) - + z_iter = tqdm(range(denoise_img.shape[0]), 'Building point cloud..', total=denoise_img.shape[0], leave=False) - + """ Compute euclidean transformation for labels and build point cloud """ for i in z_iter: picked_maxima = peak_local_max(sk_image[i, :], @@ -282,6 +282,9 @@ def find_maximas(self, if down_sampling: pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(coordinates) - coordinates = np.asarray(pcd.voxel_down_sample(voxel_size=25).points) + coordinates_ds = np.asarray( + pcd.voxel_down_sample(voxel_size=25).points) + + return denoise_img, coordinates, coordinates_ds return denoise_img, coordinates diff --git a/slcpy/utils/interpolation.py b/slcpy/utils/interpolation.py index f7dd018..afeaa68 100644 --- a/slcpy/utils/interpolation.py +++ b/slcpy/utils/interpolation.py @@ -5,7 +5,7 @@ def interpolation_1D(start: int, stop: int, max_len: int): """ - Module to build a continues segment in given 3D frame + 1D INTERPOLATION FOR BUILDING SEMANTIC MASK Args: start: 1D single coordinate to start interpolation @@ -25,7 +25,7 @@ def interpolation_1D(start: int, def interpolation_3D(points: np.ndarray): """ - Module: interpolation_3D + 3D INTERPOLATION FOR BUILDING SEMANTIC MASK Args: points: numpy array with points belonging to individual segments given diff --git a/slcpy/utils/stitch.py b/slcpy/utils/stitch.py index 7a907f1..9b0cdfe 100644 --- a/slcpy/utils/stitch.py +++ b/slcpy/utils/stitch.py @@ -8,6 +8,8 @@ class StitchImages: """ + MAIN MODULE TO STITCH IMAGE FROM IMAGE PATCHES + Class object to stitch cut date into one big image. Object recognize images with naming 1_1_1_25 where 1 indicate xyz position and 25 indicate stride. @@ -29,11 +31,16 @@ def _find_xyz(self, # Extract information about images in dir_path file_list = [f for f in listdir(dir_path) if isfile(join(dir_path, f))] - self.idx = max(list(map(int, [str.split(f[:-4], "_")[0] for f in file_list]))) - self.x = max(list(map(int, [str.split(f[:-4], "_")[1] for f in file_list]))) - self.y = max(list(map(int, [str.split(f[:-4], "_")[2] for f in file_list]))) - self.z = max(list(map(int, [str.split(f[:-4], "_")[3] for f in file_list]))) - self.stride = max(list(map(int, [str.split(f[:-4], "_")[4] for f in file_list]))) + self.idx = max( + list(map(int, [str.split(f[:-4], "_")[0] for f in file_list]))) + self.x = max( + list(map(int, [str.split(f[:-4], "_")[1] for f in file_list]))) + self.y = max( + list(map(int, [str.split(f[:-4], "_")[2] for f in file_list]))) + self.z = max( + list(map(int, [str.split(f[:-4], "_")[3] for f in file_list]))) + self.stride = max( + list(map(int, [str.split(f[:-4], "_")[4] for f in file_list]))) return file_list @@ -100,9 +107,11 @@ def __call__(self, img = tifffile.imread(img_dir) assert img.shape == (self.nz, self.ny, self.nx) if mask: - stitched_image[z_start:z_stop, y_start:y_stop, x_start:x_stop] += img + stitched_image[z_start:z_stop, + y_start:y_stop, x_start:x_stop] += img else: - stitched_image[z_start:z_stop, y_start:y_stop, x_start:x_stop] = img + stitched_image[z_start:z_stop, + y_start:y_stop, x_start:x_stop] = img img_counter += 1 return stitched_image diff --git a/slcpy/utils/trim.py b/slcpy/utils/trim.py index 3de6250..e133037 100644 --- a/slcpy/utils/trim.py +++ b/slcpy/utils/trim.py @@ -1,5 +1,4 @@ import math - from os.path import join from typing import Optional @@ -60,8 +59,8 @@ def trim_images(image: np.ndarray, for j in range(1, x_axis + 1): nx_start += trim_size_xy nx_end += trim_size_xy - img_name = str(idx) + r'.tif' - mask_name = str(idx) + r'_mask.tif' + img_name = str(idx) + '.tif' + mask_name = str(idx) + '_mask.tif' trim_img = np.zeros((trim_size_z, trim_size_xy, trim_size_xy)) @@ -176,8 +175,6 @@ def trim_to_patches(image: np.ndarray, "trim_size_z should be equal or greater then X dimension!" assert ny >= trim_size_xy, \ "trim_size_z should be equal or greater then Y dimension!" - # if nz >= trim_size_z: - # trim_size_z = nz else: assert stride is not None, \ "Trim sizes or stride has to be indicated!" diff --git a/slcpy/version.py b/slcpy/version.py index d24dc8a..ae5cb45 100644 --- a/slcpy/version.py +++ b/slcpy/version.py @@ -1 +1 @@ -version = "0.3.5" +version = "0.3.6"