diff --git a/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/image_processor.py b/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/image_processor.py index 6a34e31..dd4fa26 100644 --- a/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/image_processor.py +++ b/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/image_processor.py @@ -18,7 +18,7 @@ class ImageProcessorException(Exception): """ - Class for handling exceptions of class ImageProcessor + Class for handling exceptions for class ImageProcessor """ pass @@ -30,8 +30,7 @@ class ImageProcessor: def __init__(self, file_paths: List[str], n_channels: int = 1, - batch_size: int = 1, - image_resolution: tuple = None, + image_resolution: Tuple[int, int] = None, normalize: bool = False, flip: bool = True, crop: Tuple[Tuple[int, int], Tuple[int, int]] = None, @@ -58,10 +57,7 @@ def __init__(self, -> 0: gray -> 3: color (rgb) - :param batch_size: int - Batch size - - :param image_resolution: tuple + :param image_resolution: Tuple[int, int] Force resizing image into given resolution :param normalize: bool @@ -70,7 +66,7 @@ def __init__(self, :param flip: bool Whether to flip image based on probability distribution or not - :parm crop: Tuple[Tuple[int, int], Tuple[int, int]] + :param crop: Tuple[Tuple[int, int], Tuple[int, int]] Define image cropping :param skew: bool @@ -117,8 +113,7 @@ def __init__(self, self.file_paths: List[str] = file_paths self.file_type: str = '' if file_type is None else file_type self.n_channels: int = 3 if n_channels > 1 else 1 - self.batch_size: int = batch_size - self.image_resolution: tuple = image_resolution + self.image_resolution: Tuple[int, int] = image_resolution self.normalize: bool = normalize self.flip: bool = flip self.crop: Tuple[Tuple[int, int], Tuple[int, int]] = crop @@ -241,58 +236,15 @@ def _get_angle(self, image: np.array) -> float: _angle = 90 + _angle return -1.0 * _angle - def _load_images(self, n_images: int = None, label: int = None) -> Tuple[str, np.array]: - """ - Load images without batching - - :param n_images: int - Number of images - - :param label: int - Label number - - :return Tuple[List[str], np.array] - List of image file names & array of loaded images - """ - _images_path: List[str] = [] - _images_noisy: List[np.array] = [] - if self.file_path_multi_noisy_images is None: - _file_paths_noisy: List[str] = glob(os.path.join('.', self.file_path_noisy_images, f'*{self.file_type}')) - else: - _file_paths_noisy: List[str] = glob(os.path.join('.', self.file_path_multi_noisy_images[label], f'*{self.file_type}')) - if n_images is not None and n_images > 0: - for i in range(0, n_images, 1): - _file_paths_noisy_sample: List[str] = np.random.choice(_file_paths_noisy, 1, replace=False) - for path_image_noisy in _file_paths_noisy_sample: - _images_path.append(path_image_noisy) - _images_noisy.append(self._read_image(file_path=path_image_noisy)) - if self.normalize: - yield _images_path, self._normalize(np.array(_images_noisy)) - else: - yield _images_path, np.array(_images_noisy) - else: - self.n_batches = int(len(_file_paths_noisy)) - _total_samples: int = self.n_batches * self.batch_size - _file_paths_noisy_sample: List[str] = np.random.choice(_file_paths_noisy, _total_samples, replace=False) - for i in range(0, self.n_batches - 1, 1): - _batch_noisy: List[str] = _file_paths_noisy_sample[i * self.batch_size:(i + 1) * self.batch_size] - for path_image_noisy in _batch_noisy: - _images_path.append(path_image_noisy) - _images_noisy.append(self._read_image(file_path=path_image_noisy)) - if self.normalize: - yield _images_path, self._normalize(np.array(_images_noisy)) - else: - yield _images_path, np.array(_images_noisy) - @staticmethod - def _normalize(images: np.array) -> np.array: + def _normalize(image: np.array) -> np.array: """ - Normalize images + Normalize image - :param images: np.array - Images + :param image: np.array + Image data """ - return images / 255 + return image / 255 def _read_image(self, file_path: str) -> np.array: """ @@ -304,19 +256,7 @@ def _read_image(self, file_path: str) -> np.array: :return np.array Image """ - _image: np.array = load_file_from_s3(file_path=file_path, image_channels=self.n_channels) - if self.crop is not None: - _image = _image[self.crop[0][0]:self.crop[0][1], self.crop[1][0]:self.crop[1][1]] - if self.image_resolution is not None: - _image = resize(image=_image, output_shape=self.image_resolution) - if self.flip: - if np.random.random() > 0.5: - if np.random.random() > 0.5: - _direction: int = 0 - else: - _direction: int = 1 - _image = cv2.flip(src=_image, flipCode=_direction, dst=None) - return _image + return load_file_from_s3(file_path=file_path, image_channels=self.n_channels) @staticmethod def _rotate_image(image: np.array, angle: float) -> np.array: @@ -443,6 +383,17 @@ def main(self, save_as_image: bool = True, image_path: str = None, file_path_arr _images: List[np.ndarray] = [] for image_file_path in self.file_paths: _image: np.ndarray = self._read_image(file_path=image_file_path) + if self.crop is not None: + _image = _image[self.crop[0][0]:self.crop[0][1], self.crop[1][0]:self.crop[1][1]] + if self.image_resolution is not None: + _image = resize(image=_image, output_shape=self.image_resolution) + if self.flip: + if np.random.random() > 0.5: + if np.random.random() > 0.5: + _direction: int = 0 + else: + _direction: int = 1 + _image = cv2.flip(src=_image, flipCode=_direction, dst=None) if self.skew: _image = self._skew(image=_image) if self.deskew: @@ -455,9 +406,14 @@ def main(self, save_as_image: bool = True, image_path: str = None, file_path_arr _image = self._salt_pepper(image=_image) if self.generate_noise_watermark: _image = self._watermark(image=_image) + if self.normalize: + _image = self._normalize(image=_image) if save_as_image: - save_file_to_s3(file_path=image_path, obj=_image) + _file_name: str = os.path.join(image_path, image_file_path.split('/')[-1]) + save_file_to_s3(file_path=_file_name, obj=_image) + Log().log(msg=f'Save image: {_file_name}') else: _images.append(_image) if len(_images) > 0: save_file_to_s3(file_path=file_path_array, obj=_images) + Log().log(msg=f'Save images: {file_path_array}') diff --git a/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/task.py b/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/task.py index f913d0a..e384b52 100644 --- a/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/task.py +++ b/kubeflow_templates/kubeflow_pipelines/task_pool/v1/image_processor/task_container/src/task.py @@ -6,266 +6,173 @@ import argparse -from auto_encoder import AutoEncoder -from custom_logger import Log -from cycle_gan import CycleGAN - - -PARSER = argparse.ArgumentParser(description="image translation") -PARSER.add_argument('-data_set_path', type=str, required=True, default=None, help='file path of the data set') -PARSER.add_argument('-missing_value_threshold', type=float, required=False, default=0.95, help='threshold to classify features as invalid based on the amount of missing values') -PARSER.add_argument('-sep', type=str, required=False, default=',', help='column separator') -PARSER.add_argument('-output_file_path_data_missing_data', type=str, required=True, default=None, help='file path of features containing too much missing data output') -PARSER.add_argument('-output_file_path_invariant', type=str, required=True, default=None, help='file path of invariant features output') -PARSER.add_argument('-output_file_path_duplicated', type=str, required=True, default=None, help='file path of duplicated features output') -PARSER.add_argument('-output_file_path_valid_features', type=str, required=True, default=None, help='file path of valid features output') -PARSER.add_argument('-output_file_path_prop_valid_features', type=str, required=True, default=None, help='file path of the proportion of valid features output') -PARSER.add_argument('-s3_output_file_path_data_health_check', type=str, required=False, default=None, help='S3 file path of the data health check output') +from image_processor import ImageProcessor +from typing import List, Tuple + + +PARSER = argparse.ArgumentParser(description="image processing") +PARSER.add_argument('-file_paths', type=list, required=True, default=None, help='file paths of the images') +PARSER.add_argument('-n_channels', type=int, required=False, default=1, help='number of image channels') +PARSER.add_argument('-image_resolution', type=tuple, required=False, default=None, help='image resolution') +PARSER.add_argument('-normalize', type=int, required=False, default=0, help='whether to normalize image data or not') +PARSER.add_argument('-flip', type=int, required=False, default=1, help='whether to flip image data or not') +PARSER.add_argument('-crop', type=tuple, required=False, default=None, help='image cropping configuration') +PARSER.add_argument('-skew', type=int, required=False, default=0, help='whether to skew image data or not') +PARSER.add_argument('-deskew', type=int, required=False, default=0, help='whether to deskew image data or not') +PARSER.add_argument('-denoise_by_blurring', type=int, required=False, default=0, help='whether to denoise image data by blurring or not') +PARSER.add_argument('-generate_noise_blur', type=int, required=False, default=0, help='whether to generate blur noise in image data or not') +PARSER.add_argument('-blur_type', type=str, required=False, default='average', help='blur type') +PARSER.add_argument('-blur_kernel_size', type=tuple, required=False, default=(9, 9), help='kernel size for blurring') +PARSER.add_argument('-generate_noise_fade', type=int, required=False, default=0, help='whether to generate fade noise in image data or not') +PARSER.add_argument('-fade_brightness', type=int, required=False, default=20, help='brightness of the fade noise') +PARSER.add_argument('-generate_noise_salt_pepper', type=int, required=False, default=0, help='whether to generate salt & pepper noise in image data or not') +PARSER.add_argument('-salt_pepper_number_of_pixel_edges', type=tuple, required=False, default=(100000, 500000), help='number of pixel edges used to generate salt & pepper noise') +PARSER.add_argument('-generate_noise_watermark', type=int, required=False, default=0, help='whether to generate watermark noise in image data or not') +PARSER.add_argument('-watermark_files', type=list, required=False, default=None, help='complete file paths of the watermark noise') +PARSER.add_argument('-save_as_image', type=int, required=False, default=1, help='whether to save image data as images or as a aggregated binary file') +PARSER.add_argument('-s3_output_image_path', type=str, required=False, default=None, help='S3 path of the image output') +PARSER.add_argument('-s3_output_file_path_array', type=str, required=False, default=None, help='S3 file path of the aggregated binary output') ARGS = PARSER.parse_args() -def image_translation(unpaired: bool, - train_data_set_path: str, - test_data_set_path: str = None, - n_channels: int = 1, - image_height: int = 256, - image_width: int = 256, - learning_rate: float = 0.0002, - optimizer: str = 'adam', - initializer: str = 'he_normal', - batch_size: int = 1, - start_n_filters_discriminator: int = 64, - max_n_filters_discriminator: int = 512, - n_conv_layers_discriminator: int = 3, - dropout_rate_discriminator: float = 0.0, - start_n_filters_generator: int = 32, - max_n_filters_generator: int = 512, - up_sample_n_filters_period: int = 0, - generator_type: str = 'res', - n_res_net_blocks: int = 6, - n_conv_layers_generator_res_net: int = 2, - n_conv_layers_generator_u_net: int = 3, - dropout_rate_generator_res_net: float = 0.0, - dropout_rate_generator_down_sampling: float = 0.0, - dropout_rate_generator_up_sampling: float = 0.0, - include_moe_layers: bool = False, - start_n_filters_moe_embedder: int = 32, - n_conv_layers_moe_embedder: int = 7, - max_n_filters_embedder: int = 64, - dropout_rate_moe_embedder: float = 0.0, - n_embedding_features: int = 64, - gate_after_each_conv_res_net_block: bool = True, - n_hidden_layers_moe_fc_gated_net: int = 1, - n_hidden_layers_moe_fc_classifier: int = 1, - dropout_rate_moe_fc_gated_net: float = 0.0, - n_noise_types_moe_fc_classifier: int = 4, - dropout_rate_moe_fc_classifier: float = 0.0, - print_model_architecture: bool = True, - s3_output_file_path_model_artifact: str = None - ) -> None: +def image_processor(file_paths: List[str], + n_channels: int = 1, + image_resolution: Tuple[int, int] = None, + normalize: bool = False, + flip: bool = True, + crop: Tuple[Tuple[int, int], Tuple[int, int]] = None, + skew: bool = False, + deskew: bool = False, + denoise_by_blurring: bool = False, + generate_noise_blur: bool = False, + blur_type: str = 'average', + blur_kernel_size: Tuple[int, int] = (9, 9), + generate_noise_fade: bool = False, + fade_brightness: int = 20, + generate_noise_salt_pepper: bool = False, + salt_pepper_number_of_pixel_edges: Tuple[int, int] = (100000, 500000), + generate_noise_watermark: bool = False, + watermark_files: List[str] = None, + save_as_image: bool = True, + s3_output_image_path: str = None, + s3_output_file_path_array: str = None + ) -> None: """ - Translate paired or unpaired image characteristics - - :param n_channels: int - Number of image channels - -> 1: gray - -> 3: color (rbg) - - :param image_height: int - Height of the image - - :param image_width: int - Width of the image - - :param learning_rate: float - Learning rate - - :param initializer: str - Name of the initializer used in convolutional layers - -> constant: Constant value 2 - -> he_normal: - -> he_uniform: - -> glorot_normal: Xavier normal - -> glorot_uniform: Xavier uniform - -> lecun_normal: Lecun normal - -> lecun_uniform: - -> ones: Constant value 1 - -> orthogonal: - -> random_normal: - -> random_uniform: - -> truncated_normal: - -> zeros: Constant value 0 - - :param batch_size: int - Batch size + Process images - :param start_n_filters_discriminator: int - Number of filters used in first convolutional layer in discriminator network + :param file_paths: str + Complete file path of the images - :param max_n_filters_discriminator: int - Maximum number of filter used in all convolutional layers in discriminator network - - :param n_conv_layers_discriminator: int - Number of convolutional layers in discriminator network - - :param dropout_rate_discriminator: float - Dropout rate used after each convolutional layer in discriminator network - - :param start_n_filters_generator: int - Number of filters used in first convolutional layer in generator network - - :param max_n_filters_generator: int - Maximum number of filter used in all convolutional layers in generator network - - :param up_sample_n_filters_period: int - Number of layers until up-sampling number of filters - - :param generator_type: str - Abbreviated name of the type of the generator - -> u: U-Network architecture - -> resnet: Residual network architecture - - :param n_res_net_blocks: int - Number of residual network blocks to use - Common: -> 6, 9 + :param n_channels: int + Number of channels of the image + -> 0: gray + -> 3: color (rgb) - :param n_conv_layers_generator_res_net: int - Number of convolutional layers used for down and up sampling + :param image_resolution: Tuple[int, int] + Force resizing image into given resolution - :param n_conv_layers_generator_u_net: int - Number of convolutional layers in generator network with u-net architecture + :param normalize: bool + Whether to normalize image (rescale to 0 - 1) or not - :param dropout_rate_generator_res_net: float - Dropout rate used after each convolutional layer in generator residual network + :param flip: bool + Whether to flip image based on probability distribution or not - :param dropout_rate_generator_down_sampling: float - Dropout rate used after each convolutional layer in generator down-sampling network + :param crop: Tuple[Tuple[int, int], Tuple[int, int]] + Define image cropping - :param dropout_rate_generator_up_sampling: float - Dropout rate used after each convolutional layer in generator up-sampling network + :param skew: bool + Whether to skew image or not - :param include_moe_layers: bool - Whether to use mixture of experts layers in residual network architecture + :param deskew: bool + Whether to deskew image or not - :param start_n_filters_moe_embedder: int - Number of filters used in first convolutional layer in embedding network + :param denoise_by_blurring: bool + Whether to skew image or not - :param n_conv_layers_moe_embedder: int - Number of convolutional layers in discriminator network + :param generate_noise_blur: bool + Whether to generate blur noise or not - :param max_n_filters_embedder: int - Maximum number of filters in embedder + :param blur_type: str + Blur type name + -> average: Average + -> median: Median - :param dropout_rate_moe_embedder: float - Dropout rate used after each convolutional layer in mixture of experts embedder network + :param blur_kernel_size: Tuple[int, int] + Kernel size - :param n_embedding_features: int - Number of embedding output features + :param generate_noise_fade: bool + Whether to generate fade noise or not - :param gate_after_each_conv_res_net_block: bool - Whether to use gated network after each convolutional layer in residual network block or just in the end + :param fade_brightness: int + Desired brightness change - :param n_hidden_layers_moe_fc_gated_net: int - Number of hidden layers of the fully connected gated network used to process mixture of experts embedding output + :param generate_noise_salt_pepper: bool + Whether to generate salt and pepper noise or not - :param n_hidden_layers_moe_fc_classifier: int - Number of hidden layers of the fully connected gated network used to classify mixture of experts embedding output (noise type) + :param salt_pepper_number_of_pixel_edges: tuple + Edges to draw number of pixels to coloring into white and black - :param dropout_rate_moe_fc_gated_net: float - Dropout rate used after each convolutional layer in mixture of experts fully connected gated network + :param generate_noise_watermark: bool + Whether to generate watermark noise or not - :param n_noise_types_moe_fc_classifier: int - Number of classes (noise types) to classify + :param watermark_files: List[str] + Complete file path of the files containing watermarks - :param dropout_rate_moe_fc_classifier: float - Dropout rate used after each convolutional layer in mixture of experts fully connected classification network + :param save_as_image: bool + Whether to save processed image as image or multi-dimensional array - :param print_model_architecture: bool - Whether to print architecture of cycle-gan model components (discriminators & generators) or not + :param s3_output_image_path: str + Path of the image output - :param kwargs: dict - Key-word arguments for class ImageProcessor and compiling model configuration + :param s3_output_file_path_array: str + Complete file path of the processed image array output """ - if unpaired: - _cycle_gan: CycleGAN = CycleGAN(file_path_train_clean_images=None, - file_path_train_noisy_images=None, - file_path_eval_noisy_images=None, - file_path_moe_noisy_images=None, - n_channels=n_channels, - image_height=image_height, - image_width=image_width, - learning_rate=learning_rate, - optimizer=optimizer, - initializer=initializer, - batch_size=batch_size, - start_n_filters_discriminator=start_n_filters_discriminator, - max_n_filters_discriminator=max_n_filters_discriminator, - n_conv_layers_discriminator=n_conv_layers_discriminator, - dropout_rate_discriminator=dropout_rate_discriminator, - start_n_filters_generator=start_n_filters_generator, - max_n_filters_generator=max_n_filters_generator, - up_sample_n_filters_period=up_sample_n_filters_period, - generator_type=generator_type, - n_res_net_blocks=n_res_net_blocks, - n_conv_layers_generator_res_net=n_conv_layers_generator_res_net, - n_conv_layers_generator_u_net=n_conv_layers_generator_u_net, - dropout_rate_generator_res_net=dropout_rate_generator_res_net, - dropout_rate_generator_down_sampling=dropout_rate_generator_down_sampling, - dropout_rate_generator_up_sampling=dropout_rate_generator_up_sampling, - include_moe_layers=include_moe_layers, - start_n_filters_moe_embedder=start_n_filters_moe_embedder, - n_conv_layers_moe_embedder=n_conv_layers_moe_embedder, - max_n_filters_embedder=max_n_filters_embedder, - dropout_rate_moe_embedder=dropout_rate_moe_embedder, - n_embedding_features=n_embedding_features, - gate_after_each_conv_res_net_block=gate_after_each_conv_res_net_block, - n_hidden_layers_moe_fc_gated_net=n_hidden_layers_moe_fc_gated_net, - n_hidden_layers_moe_fc_classifier=n_hidden_layers_moe_fc_classifier, - dropout_rate_moe_fc_gated_net=dropout_rate_moe_fc_gated_net, - n_noise_types_moe_fc_classifier=n_noise_types_moe_fc_classifier, - dropout_rate_moe_fc_classifier=dropout_rate_moe_fc_classifier, - print_model_architecture=print_model_architecture - ) - _cycle_gan.train(model_output_path=s3_output_file_path_model_artifact, - n_epoch=300, - asynchron=False, - discriminator_batch_size=100, - generator_batch_size=10, - early_stopping_batch=0, - random_noise_types=False, - checkpoint_epoch_interval=5, - evaluation_epoch_interval=1 - ) - else: - _auto_encoder: AutoEncoder = AutoEncoder(file_path_train_clean_images='', - file_path_train_noisy_images='', - n_channels=n_channels, - image_height=image_height, - image_width=image_width, - learning_rate=learning_rate, - optimizer=optimizer, - initializer=initializer, - batch_size=batch_size, - start_n_filters=64, - n_conv_layers=3, - dropout_rate=0.0, - print_model_architecture=print_model_architecture - ) - _auto_encoder.train(model_output_path=s3_output_file_path_model_artifact, - n_epoch=30, - early_stopping_patience=0 - ) + _image_processor: ImageProcessor = ImageProcessor(file_paths=file_paths, + n_channels=n_channels, + image_resolution=image_resolution, + normalize=normalize, + flip=flip, + crop=crop, + skew=skew, + deskew=deskew, + denoise_by_blurring=denoise_by_blurring, + generate_noise_blur=generate_noise_blur, + blur_type=blur_type, + blur_kernel_size=blur_kernel_size, + generate_noise_fade=generate_noise_fade, + fade_brightness=fade_brightness, + generate_noise_salt_pepper=generate_noise_salt_pepper, + salt_pepper_number_of_pixel_edges=salt_pepper_number_of_pixel_edges, + generate_noise_watermark=generate_noise_watermark, + watermark_files=watermark_files, + file_type=None + ) + _image_processor.main(save_as_image=save_as_image, + image_path=s3_output_image_path, + file_path_array=s3_output_file_path_array + ) if __name__ == '__main__': - data_health_check(data_set_path=ARGS.data_set_path, - analytical_data_types=ARGS.analytical_data_types, - output_file_path_missing_data=ARGS.output_file_path_missing_data, - output_file_path_invariant=ARGS.output_file_path_invariant, - output_file_path_duplicated=ARGS.output_file_path_duplicated, - output_file_path_valid_features=ARGS.output_file_path_valid_features, - output_file_path_prop_valid_features=ARGS.output_file_path_prop_valid_features, - missing_value_threshold=ARGS.missing_value_threshold, - sep=ARGS.sep, - s3_output_file_path_data_health_check=ARGS.s3_output_file_path_data_health_check - ) + image_processor(file_paths=ARGS.file_paths, + n_channels=ARGS.n_channels, + image_resolution=ARGS.image_resolution, + normalize=ARGS.normalize, + flip=ARGS.flip, + crop=ARGS.crop, + skew=ARGS.skew, + deskew=ARGS.deskew, + denoise_by_blurring=ARGS.denoise_by_blurring, + generate_noise_blur=ARGS.generate_noise_blur, + blur_type=ARGS.blur_type, + blur_kernel_size=ARGS.blur_kernel_size, + generate_noise_fade=ARGS.generate_noise_fade, + fade_brightness=ARGS.fade_brightness, + generate_noise_salt_pepper=ARGS.generate_noise_salt_pepper, + salt_pepper_number_of_pixel_edges=ARGS.salt_pepper_number_of_pixel_edges, + generate_noise_watermark=ARGS.generate_noise_watermark, + watermark_files=ARGS.watermark_files, + save_as_image=ARGS.save_as_image, + s3_output_image_path=ARGS.s3_output_image_path, + s3_output_file_path_array=ARGS.s3_output_file_path_array + )