From 875e222242ad7ef0b947e00701023b8b86402ce2 Mon Sep 17 00:00:00 2001 From: zhangmozhe Date: Mon, 21 Dec 2020 23:17:29 +0800 Subject: [PATCH] update training code --- .vscode/.ropeproject/config.py | 114 ++++ .vscode/.ropeproject/objectdb | Bin 0 -> 6 bytes .vscode/launch.json | 15 + README.md | 13 + bash.sh | 30 + lib/TrainTransforms.py | 348 +++++++++++ lib/videoloader.py | 301 ++++++++++ lib/videoloader_imagenet.py | 290 ++++++++++ models/ContextualLoss.py | 303 ++++++++++ models/GAN_models.py | 414 ++++++------- models/NonlocalNet.py | 4 +- opt.txt | 37 ++ requirements.txt | 3 +- sample_videos/output/v32_01/00000.jpg | Bin 0 -> 20749 bytes sample_videos/output/v32_01/00001.jpg | Bin 0 -> 20874 bytes sample_videos/output/v32_01/00002.jpg | Bin 0 -> 20882 bytes sample_videos/output/v32_01/00003.jpg | Bin 0 -> 21030 bytes sample_videos/output/v32_01/00004.jpg | Bin 0 -> 21026 bytes sample_videos/output/v32_01/00005.jpg | Bin 0 -> 21113 bytes sample_videos/output/v32_01/00006.jpg | Bin 0 -> 21141 bytes sample_videos/output/v32_01/00007.jpg | Bin 0 -> 21079 bytes sample_videos/output/v32_01/00008.jpg | Bin 0 -> 21142 bytes sample_videos/output/v32_01/00009.jpg | Bin 0 -> 21041 bytes sample_videos/output/v32_01/00010.jpg | Bin 0 -> 21168 bytes sample_videos/output/v32_01/00011.jpg | Bin 0 -> 21280 bytes sample_videos/output/v32_01/00012.jpg | Bin 0 -> 21350 bytes sample_videos/output/v32_01/00013.jpg | Bin 0 -> 21426 bytes tensorboardX/__init__.py | 5 + tensorboardX/crc32c.py | 123 ++++ tensorboardX/embedding.py | 49 ++ tensorboardX/event_file_writer.py | 196 +++++++ tensorboardX/graph.py | 66 +++ tensorboardX/graph_onnx.py | 158 +++++ tensorboardX/record_writer.py | 47 ++ tensorboardX/src/__init__.py | 0 tensorboardX/src/attr_value_pb2.py | 372 ++++++++++++ tensorboardX/src/event_pb2.py | 376 ++++++++++++ tensorboardX/src/graph_pb2.py | 92 +++ tensorboardX/src/node_def_pb2.py | 151 +++++ tensorboardX/src/plugin_pr_curve_pb2.py | 76 +++ tensorboardX/src/resource_handle_pb2.py | 99 ++++ tensorboardX/src/summary_pb2.py | 532 +++++++++++++++++ tensorboardX/src/tensor_pb2.py | 185 ++++++ tensorboardX/src/tensor_shape_pb2.py | 125 ++++ tensorboardX/src/types_pb2.py | 254 ++++++++ tensorboardX/src/versions_pb2.py | 85 +++ tensorboardX/summary.py | 264 +++++++++ tensorboardX/writer.py | 485 ++++++++++++++++ tensorboardX/x2num.py | 90 +++ test.py | 12 +- train.py | 738 ++++++++++++++++++++++++ utils/flowlib.py | 523 +++++++++++++++++ utils/tb_image_recorder.py | 28 + utils/util_tensorboard.py | 43 ++ utils/warping.py | 30 + warping.py | 30 + 56 files changed, 6891 insertions(+), 215 deletions(-) create mode 100644 .vscode/.ropeproject/config.py create mode 100644 .vscode/.ropeproject/objectdb create mode 100644 .vscode/launch.json create mode 100644 bash.sh create mode 100644 lib/TrainTransforms.py create mode 100644 lib/videoloader.py create mode 100644 lib/videoloader_imagenet.py create mode 100644 models/ContextualLoss.py create mode 100644 opt.txt create mode 100644 sample_videos/output/v32_01/00000.jpg create mode 100644 sample_videos/output/v32_01/00001.jpg create mode 100644 sample_videos/output/v32_01/00002.jpg create mode 100644 sample_videos/output/v32_01/00003.jpg create mode 100644 sample_videos/output/v32_01/00004.jpg create mode 100644 sample_videos/output/v32_01/00005.jpg create mode 100644 sample_videos/output/v32_01/00006.jpg create mode 100644 sample_videos/output/v32_01/00007.jpg create mode 100644 sample_videos/output/v32_01/00008.jpg create mode 100644 sample_videos/output/v32_01/00009.jpg create mode 100644 sample_videos/output/v32_01/00010.jpg create mode 100644 sample_videos/output/v32_01/00011.jpg create mode 100644 sample_videos/output/v32_01/00012.jpg create mode 100644 sample_videos/output/v32_01/00013.jpg create mode 100644 tensorboardX/__init__.py create mode 100644 tensorboardX/crc32c.py create mode 100644 tensorboardX/embedding.py create mode 100644 tensorboardX/event_file_writer.py create mode 100644 tensorboardX/graph.py create mode 100644 tensorboardX/graph_onnx.py create mode 100644 tensorboardX/record_writer.py create mode 100644 tensorboardX/src/__init__.py create mode 100644 tensorboardX/src/attr_value_pb2.py create mode 100644 tensorboardX/src/event_pb2.py create mode 100644 tensorboardX/src/graph_pb2.py create mode 100644 tensorboardX/src/node_def_pb2.py create mode 100644 tensorboardX/src/plugin_pr_curve_pb2.py create mode 100644 tensorboardX/src/resource_handle_pb2.py create mode 100644 tensorboardX/src/summary_pb2.py create mode 100644 tensorboardX/src/tensor_pb2.py create mode 100644 tensorboardX/src/tensor_shape_pb2.py create mode 100644 tensorboardX/src/types_pb2.py create mode 100644 tensorboardX/src/versions_pb2.py create mode 100644 tensorboardX/summary.py create mode 100644 tensorboardX/writer.py create mode 100644 tensorboardX/x2num.py create mode 100644 train.py create mode 100644 utils/flowlib.py create mode 100644 utils/tb_image_recorder.py create mode 100644 utils/util_tensorboard.py create mode 100644 utils/warping.py create mode 100644 warping.py diff --git a/.vscode/.ropeproject/config.py b/.vscode/.ropeproject/config.py new file mode 100644 index 0000000..dee2d1a --- /dev/null +++ b/.vscode/.ropeproject/config.py @@ -0,0 +1,114 @@ +# The default ``config.py`` +# flake8: noqa + + +def set_prefs(prefs): + """This function is called before opening the project""" + + # Specify which files and folders to ignore in the project. + # Changes to ignored resources are not added to the history and + # VCSs. Also they are not returned in `Project.get_files()`. + # Note that ``?`` and ``*`` match all characters but slashes. + # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' + # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' + # '.svn': matches 'pkg/.svn' and all of its children + # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' + # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' + prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', + '.hg', '.svn', '_svn', '.git', '.tox'] + + # Specifies which files should be considered python files. It is + # useful when you have scripts inside your project. Only files + # ending with ``.py`` are considered to be python files by + # default. + # prefs['python_files'] = ['*.py'] + + # Custom source folders: By default rope searches the project + # for finding source folders (folders that should be searched + # for finding modules). You can add paths to that list. Note + # that rope guesses project source folders correctly most of the + # time; use this if you have any problems. + # The folders should be relative to project root and use '/' for + # separating folders regardless of the platform rope is running on. + # 'src/my_source_folder' for instance. + # prefs.add('source_folders', 'src') + + # You can extend python path for looking up modules + # prefs.add('python_path', '~/python/') + + # Should rope save object information or not. + prefs['save_objectdb'] = True + prefs['compress_objectdb'] = False + + # If `True`, rope analyzes each module when it is being saved. + prefs['automatic_soa'] = True + # The depth of calls to follow in static object analysis + prefs['soa_followed_calls'] = 0 + + # If `False` when running modules or unit tests "dynamic object + # analysis" is turned off. This makes them much faster. + prefs['perform_doa'] = True + + # Rope can check the validity of its object DB when running. + prefs['validate_objectdb'] = True + + # How many undos to hold? + prefs['max_history_items'] = 32 + + # Shows whether to save history across sessions. + prefs['save_history'] = True + prefs['compress_history'] = False + + # Set the number spaces used for indenting. According to + # :PEP:`8`, it is best to use 4 spaces. Since most of rope's + # unit-tests use 4 spaces it is more reliable, too. + prefs['indent_size'] = 4 + + # Builtin and c-extension modules that are allowed to be imported + # and inspected by rope. + prefs['extension_modules'] = [] + + # Add all standard c-extensions to extension_modules list. + prefs['import_dynload_stdmods'] = True + + # If `True` modules with syntax errors are considered to be empty. + # The default value is `False`; When `False` syntax errors raise + # `rope.base.exceptions.ModuleSyntaxError` exception. + prefs['ignore_syntax_errors'] = False + + # If `True`, rope ignores unresolvable imports. Otherwise, they + # appear in the importing namespace. + prefs['ignore_bad_imports'] = False + + # If `True`, rope will insert new module imports as + # `from import ` by default. + prefs['prefer_module_from_imports'] = False + + # If `True`, rope will transform a comma list of imports into + # multiple separate import statements when organizing + # imports. + prefs['split_imports'] = False + + # If `True`, rope will remove all top-level import statements and + # reinsert them at the top of the module when making changes. + prefs['pull_imports_to_top'] = True + + # If `True`, rope will sort imports alphabetically by module name instead + # of alphabetically by import statement, with from imports after normal + # imports. + prefs['sort_imports_alphabetically'] = False + + # Location of implementation of + # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general + # case, you don't have to change this value, unless you're an rope expert. + # Change this value to inject you own implementations of interfaces + # listed in module rope.base.oi.type_hinting.providers.interfaces + # For example, you can add you own providers for Django Models, or disable + # the search type-hinting in a class hierarchy, etc. + prefs['type_hinting_factory'] = ( + 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') + + +def project_opened(project): + """This function is called after opening the project""" + # Do whatever you like here! diff --git a/.vscode/.ropeproject/objectdb b/.vscode/.ropeproject/objectdb new file mode 100644 index 0000000000000000000000000000000000000000..0a47446c0ad231c193bdd44ff327ba2ab28bf3d8 GIT binary patch literal 6 NcmZo*sx4&D0{{kv0iOT> literal 0 HcmV?d00001 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..01adf0b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + } + ] +} diff --git a/README.md b/README.md index 1458044..1481cb9 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,18 @@ python test.py --clip_path ./sample_videos/clips/v32 \ Note that we use 216\*384 images for training, which has aspect ratio of 1:2. During inference, we scale the input to this size and then rescale the output back to the original size. +## Train + +We also provide training code for reference. The training can be started by running: + +```bash +python --data_root [root of video samples] \ + --data_root_imagenet [root of image samples] \ + --gpu_ids [gpu ids] \ +``` + +We do not provide the full video dataset due to the copyright issue. For image samples, we retrieve semantically similar images from ImageNet using [this repository](https://github.com/hmmlillian/Gray-Image-Retrieval). Still, one can refer to our code to understand the detailed procedure of augmenting the image dataset to mimic the video frames. + ## Comparison with State-of-the-Arts
@@ -73,6 +85,7 @@ Note that we use 216\*384 images for training, which has aspect ratio of 1:2. Du
## More results +
diff --git a/bash.sh b/bash.sh new file mode 100644 index 0000000..9aaca92 --- /dev/null +++ b/bash.sh @@ -0,0 +1,30 @@ +python test.py --clip_path ../test_videos/frames/0 --ref_path ../test_videos/refs/0 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/1 --ref_path ../test_videos/refs/1 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/2 --ref_path ../test_videos/refs/2 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/3 --ref_path ../test_videos/refs/3 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/4 --ref_path ../test_videos/refs/4 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/5 --ref_path ../test_videos/refs/5 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/6 --ref_path ../test_videos/refs/6 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/7 --ref_path ../test_videos/refs/7 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/8 --ref_path ../test_videos/refs/8 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/9 --ref_path ../test_videos/refs/9 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/10 --ref_path ../test_videos/refs/10 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/11 --ref_path ../test_videos/refs/11 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/12 --ref_path ../test_videos/refs/12 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/13 --ref_path ../test_videos/refs/13 --output_path ../test_videos/output + +python test.py --clip_path ../test_videos/frames/14 --ref_path ../test_videos/refs/14 --output_path ../test_videos/output + diff --git a/lib/TrainTransforms.py b/lib/TrainTransforms.py new file mode 100644 index 0000000..1737927 --- /dev/null +++ b/lib/TrainTransforms.py @@ -0,0 +1,348 @@ +from __future__ import division + +import collections +import numbers +import random + +import torch +from PIL import Image +from skimage import color + +import lib.functional as F + +__all__ = [ + "Compose", + "Concatenate", + "ToTensor", + "Normalize", + "Resize", + "Scale", + "CenterCrop", + "Pad", + "RandomCrop", + "RandomHorizontalFlip", + "RandomVerticalFlip", + "RandomResizedCrop", + "RandomSizedCrop", + "FiveCrop", + "TenCrop", + "RGB2Lab", +] + + +def CustomFunc(inputs, func, *args, **kwargs): + im_l = func(inputs[0], *args, **kwargs) + im_ab = func(inputs[1], *args, **kwargs) + warp_ba = func(inputs[2], *args, **kwargs) + warp_aba = func(inputs[3], *args, **kwargs) + im_gbl_ab = func(inputs[4], *args, **kwargs) + bgr_mc_im = func(inputs[5], *args, **kwargs) + + layer_data = [im_l, im_ab, warp_ba, warp_aba, im_gbl_ab, bgr_mc_im] + + for l in range(5): + layer = inputs[6 + l] + err_ba = func(layer[0], *args, **kwargs) + err_ab = func(layer[1], *args, **kwargs) + + layer_data.append([err_ba, err_ab]) + + return layer_data + + +class Compose(object): + """Composes several transforms together. + + Args: + transforms (list of ``Transform`` objects): list of transforms to compose. + + Example: + >>> transforms.Compose([ + >>> transforms.CenterCrop(10), + >>> transforms.ToTensor(), + >>> ]) + """ + + def __init__(self, transforms): + self.transforms = transforms + + def __call__(self, inputs): + for t in self.transforms: + inputs = t(inputs) + return inputs + + +class Concatenate(object): + """ + Input: [im_l, im_ab, inputs] + inputs = [warp_ba_l, warp_ba_ab, warp_aba, err_pm, err_aba] + + Output:[im_l, err_pm, warp_ba, warp_aba, im_ab, err_aba] + """ + + def __call__(self, inputs): + im_l = inputs[0] + im_ab = inputs[1] + warp_ba = inputs[2] + warp_aba = inputs[3] + im_glb_ab = inputs[4] + bgr_mc_im = inputs[5] + bgr_mc_im = bgr_mc_im[[2, 1, 0], ...] + + err_ba = [] + err_ab = [] + + for l in range(5): + layer = inputs[6 + l] + err_ba.append(layer[0]) + err_ab.append(layer[1]) + + cerr_ba = torch.cat(err_ba, 0) + cerr_ab = torch.cat(err_ab, 0) + + return (im_l, cerr_ba, warp_ba, warp_aba, im_glb_ab, bgr_mc_im, im_ab, cerr_ab) + + +class ToTensor(object): + """Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. + + Converts a PIL Image or numpy.ndarray (H x W x C) in the range + [0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0]. + """ + + def __call__(self, inputs): + """ + Args: + pic (PIL Image or numpy.ndarray): Image to be converted to tensor. + + Returns: + Tensor: Converted image. + """ + return CustomFunc(inputs, F.to_mytensor) + + +class Normalize(object): + """Normalize an tensor image with mean and standard deviation. + Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels, this transform + will normalize each channel of the input ``torch.*Tensor`` i.e. + ``input[channel] = (input[channel] - mean[channel]) / std[channel]`` + + Args: + mean (sequence): Sequence of means for each channel. + std (sequence): Sequence of standard deviations for each channel. + """ + + def __call__(self, inputs): + """ + Args: + tensor (Tensor): Tensor image of size (C, H, W) to be normalized. + + Returns: + Tensor: Normalized Tensor image. + """ + + im_l = F.normalize(inputs[0], 50, 1) # [0, 100] + im_ab = F.normalize(inputs[1], (0, 0), (1, 1)) # [-100, 100] + + inputs[2][0:1, :, :] = F.normalize(inputs[2][0:1, :, :], 50, 1) + inputs[2][1:3, :, :] = F.normalize(inputs[2][1:3, :, :], (0, 0), (1, 1)) + warp_ba = inputs[2] + + inputs[3][0:1, :, :] = F.normalize(inputs[3][0:1, :, :], 50, 1) + inputs[3][1:3, :, :] = F.normalize(inputs[3][1:3, :, :], (0, 0), (1, 1)) + warp_aba = inputs[3] + + im_gbl_ab = F.normalize(inputs[4], (0, 0), (1, 1)) # [-100, 100] + + bgr_mc_im = F.normalize(inputs[5], (123.68, 116.78, 103.938), (1, 1, 1)) + + layer_data = [im_l, im_ab, warp_ba, warp_aba, im_gbl_ab, bgr_mc_im] + + for l in range(5): + layer = inputs[6 + l] + err_ba = F.normalize(layer[0], 127, 2) # [0, 255] + err_ab = F.normalize(layer[1], 127, 2) # [0, 255] + layer_data.append([err_ba, err_ab]) + + return layer_data + + +class Resize(object): + """Resize the input PIL Image to the given size. + + Args: + size (sequence or int): Desired output size. If size is a sequence like + (h, w), output size will be matched to this. If size is an int, + smaller edge of the image will be matched to this number. + i.e, if height > width, then image will be rescaled to + (size * height / width, size) + interpolation (int, optional): Desired interpolation. Default is + ``PIL.Image.BILINEAR`` + """ + + def __init__(self, size, interpolation=Image.BILINEAR): + assert isinstance(size, int) or (isinstance(size, collections.Iterable) and len(size) == 2) + self.size = size + self.interpolation = interpolation + + def __call__(self, inputs): + """ + Args: + img (PIL Image): Image to be scaled. + + Returns: + PIL Image: Rescaled image. + """ + return CustomFunc(inputs, F.resize, self.size, self.interpolation) + + +class RandomCrop(object): + """Crop the given PIL Image at a random location. + + Args: + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. + padding (int or sequence, optional): Optional padding on each border + of the image. Default is 0, i.e no padding. If a sequence of length + 4 is provided, it is used to pad left, top, right, bottom borders + respectively. + """ + + def __init__(self, size, padding=0): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + @staticmethod + def get_params(img, output_size): + """Get parameters for ``crop`` for a random crop. + + Args: + img (PIL Image): Image to be cropped. + output_size (tuple): Expected output size of the crop. + + Returns: + tuple: params (i, j, h, w) to be passed to ``crop`` for random crop. + """ + w, h = img.size + th, tw = output_size + if w == tw and h == th: + return 0, 0, h, w + + i = random.randint(0, h - th) + j = random.randint(0, w - tw) + return i, j, th, tw + + def __call__(self, inputs): + """ + Args: + img (PIL Image): Image to be cropped. + + Returns: + PIL Image: Cropped image. + """ + if self.padding > 0: + inputs = CustomFunc(inputs, F.pad, self.padding) + + i, j, h, w = self.get_params(inputs[0], self.size) + return CustomFunc(inputs, F.crop, i, j, h, w) + + +class CenterCrop(object): + """Crop the given PIL Image at a random location. + + Args: + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. + padding (int or sequence, optional): Optional padding on each border + of the image. Default is 0, i.e no padding. If a sequence of length + 4 is provided, it is used to pad left, top, right, bottom borders + respectively. + """ + + def __init__(self, size, padding=0): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + @staticmethod + def get_params(img, output_size): + """Get parameters for ``crop`` for a random crop. + + Args: + img (PIL Image): Image to be cropped. + output_size (tuple): Expected output size of the crop. + + Returns: + tuple: params (i, j, h, w) to be passed to ``crop`` for random crop. + """ + w, h = img.size + th, tw = output_size + if w == tw and h == th: + return 0, 0, h, w + + i = (h - th) // 2 + j = (w - tw) // 2 + return i, j, th, tw + + def __call__(self, inputs): + """ + Args: + img (PIL Image): Image to be cropped. + + Returns: + PIL Image: Cropped image. + """ + if self.padding > 0: + inputs = CustomFunc(inputs, F.pad, self.padding) + + i, j, h, w = self.get_params(inputs[0], self.size) + return CustomFunc(inputs, F.crop, i, j, h, w) + + +class RandomHorizontalFlip(object): + """Horizontally flip the given PIL Image randomly with a probability of 0.5.""" + + def __call__(self, inputs): + """ + Args: + img (PIL Image): Image to be flipped. + + Returns: + PIL Image: Randomly flipped image. + """ + + if random.random() < 0.5: + return CustomFunc(inputs, F.hflip) + return inputs + + +class RGB2Lab(object): + def __call__(self, inputs): + """ + Args: + img (PIL Image): Image to be flipped. + + Returns: + PIL Image: Randomly flipped image. + """ + + def __call__(self, inputs): + image_lab = color.rgb2lab(inputs[0]) + warp_ba_lab = color.rgb2lab(inputs[2]) + warp_aba_lab = color.rgb2lab(inputs[3]) + im_gbl_lab = color.rgb2lab(inputs[4]) + + inputs[0] = image_lab[:, :, :1] # l channel + inputs[1] = image_lab[:, :, 1:] # ab channel + inputs[2] = warp_ba_lab # lab channel + inputs[3] = warp_aba_lab # lab channel + inputs[4] = im_gbl_lab[:, :, 1:] # ab channel + + return inputs diff --git a/lib/videoloader.py b/lib/videoloader.py new file mode 100644 index 0000000..00c1d5d --- /dev/null +++ b/lib/videoloader.py @@ -0,0 +1,301 @@ +import sys + +sys.path.insert(0, "..") +import os +import random + +import cv2 +import numpy as np +import torch +import torchvision.utils as vutils +from PIL import Image +from skimage import color +from torch.autograd import Variable +from utils.flowlib import read_flow +from utils.util_distortion import CenterPad + +import lib.functional as F + +cv2.setNumThreads(0) + + +class RGB2Lab(object): + def __init__(self): + pass + + def __call__(self, inputs): + return color.rgb2lab(inputs) + + +class Normalize(object): + def __init__(self): + pass + + def __call__(self, inputs): + inputs[0:1, :, :] = F.normalize(inputs[0:1, :, :], 50, 1) + inputs[1:3, :, :] = F.normalize(inputs[1:3, :, :], (0, 0), (1, 1)) + return inputs + + +class ToTensor(object): + def __init__(self): + pass + + def __call__(self, inputs): + outputs = F.to_mytensor(inputs) # permute channel and transform to tensor + return outputs + + +class RandomErasing(object): + def __init__(self, probability=0.6, sl=0.05, sh=0.6): + self.probability = probability + self.sl = sl + self.sh = sh + + def __call__(self, img): + img = np.array(img) + if random.uniform(0, 1) > self.probability: + return Image.fromarray(img) + + area = img.shape[0] * img.shape[1] + h0 = img.shape[0] + w0 = img.shape[1] + channel = img.shape[2] + + h = int(round(random.uniform(self.sl, self.sh) * h0)) + w = int(round(random.uniform(self.sl, self.sh) * w0)) + + if w < img.shape[1] and h < img.shape[0]: + x1 = random.randint(0, img.shape[0] - h) + y1 = random.randint(0, img.shape[1] - w) + img[x1 : x1 + h, y1 : y1 + w, :] = np.random.rand(h, w, channel) * 255 + return Image.fromarray(img) + + return Image.fromarray(img) + + +class CenterCrop(object): + """ + center crop the numpy array + """ + + def __init__(self, image_size): + self.h0, self.w0 = image_size + + def __call__(self, input_numpy): + if input_numpy.ndim == 3: + h, w, channel = input_numpy.shape + output_numpy = np.zeros((self.h0, self.w0, channel)) + output_numpy = input_numpy[ + (h - self.h0) // 2 : (h - self.h0) // 2 + self.h0, (w - self.w0) // 2 : (w - self.w0) // 2 + self.w0, : + ] + else: + h, w = input_numpy.shape + output_numpy = np.zeros((self.h0, self.w0)) + output_numpy = input_numpy[ + (h - self.h0) // 2 : (h - self.h0) // 2 + self.h0, (w - self.w0) // 2 : (w - self.w0) // 2 + self.w0 + ] + return output_numpy + + +def parse_images(data_root): + image_pairs = [] + subdirs = sorted(os.listdir(data_root)) + for subdir in subdirs: + path = os.path.join(data_root, subdir) + if not os.path.isdir(path): + continue + + parse_file = os.path.join(path, "pairs_output_new.txt") + if os.path.exists(parse_file): + with open(parse_file, "r") as f: + lines = f.readlines() + for line in lines: + line = line.replace("\n", "") + ( + image1_name, + image2_name, + reference_video_name, + reference_video_name1, + reference_name1, + reference_name2, + reference_name3, + reference_name4, + reference_name5, + reference_gt1, + reference_gt2, + reference_gt3, + ) = line.split() + image1_name = image1_name.split(".")[0] + image2_name = image2_name.split(".")[0] + reference_video_name = reference_video_name.split(".")[0] + reference_video_name1 = reference_video_name1.split(".")[0] + reference_name1 = reference_name1.split(".")[0] + reference_name2 = reference_name2.split(".")[0] + reference_name3 = reference_name3.split(".")[0] + reference_name4 = reference_name4.split(".")[0] + reference_name5 = reference_name5.split(".")[0] + + reference_gt1 = reference_gt1.split(".")[0] + reference_gt2 = reference_gt2.split(".")[0] + reference_gt3 = reference_gt3.split(".")[0] + + flow_forward_name = image1_name + "_forward" + flow_backward_name = image1_name + "_backward" + mask_name = image1_name + "_mask" + + item = ( + image1_name + ".jpg", + image2_name + ".jpg", + reference_video_name + ".jpg", + reference_name1 + ".JPEG", + reference_name2 + ".JPEG", + reference_name3 + ".JPEG", + reference_name4 + ".JPEG", + reference_name5 + ".JPEG", + flow_forward_name + ".flo", + flow_backward_name + ".flo", + mask_name + ".pgm", + reference_gt1 + ".jpg", + reference_gt2 + ".jpg", + reference_gt3 + ".jpg", + path, + ) + image_pairs.append(item) + + else: + raise (RuntimeError("Error when parsing pair_output_count.txt in subfolders of: " + path + "\n")) + + return image_pairs + + +class VideosDataset(torch.utils.data.Dataset): + def __init__( + self, + data_root, + epoch, + image_size, + image_transform=None, + use_google_reference=False, + real_reference_probability=1, + nonzero_placeholder_probability=0.5, + ): + self.data_root = data_root + self.image_transform = image_transform + self.CenterPad = CenterPad(image_size) + self.ToTensor = ToTensor() + self.CenterCrop = CenterCrop(image_size) + + assert len(self.data_root) > 0, "find no dataroot" + self.epoch = epoch + self.image_pairs = parse_images(self.data_root) + self.real_len = len(self.image_pairs) + print("##### parsing image pairs in %s: %d pairs #####" % (data_root, self.real_len)) + self.image_pairs *= epoch + self.use_google_reference = use_google_reference + self.real_reference_probability = real_reference_probability + self.nonzero_placeholder_probability = nonzero_placeholder_probability + + def __getitem__(self, index): + ( + image1_name, + image2_name, + reference_video_name, + reference_name1, + reference_name2, + reference_name3, + reference_name4, + reference_name5, + flow_forward_name, + flow_backward_name, + mask_name, + reference_gt1, + reference_gt2, + reference_gt3, + path, + ) = self.image_pairs[index] + try: + I1 = Image.open(os.path.join(path, "input_pad", image1_name)) + I2 = Image.open(os.path.join(path, "input_pad", image2_name)) + + I_reference_video = Image.open( + os.path.join(path, "reference_gt", random.choice([reference_gt1, reference_gt2, reference_gt3])) + ) + I_reference_video_real = Image.open( + os.path.join( + path, + "reference", + random.choice( + [reference_name1, reference_name2, reference_name3, reference_name4, reference_name5] + ), + ) + ) + + flow_forward = read_flow(os.path.join(path, "flow", flow_forward_name)) # numpy + flow_backward = read_flow(os.path.join(path, "flow", flow_backward_name)) # numpy + mask = Image.open(os.path.join(path, "mask", mask_name)) + + # binary mask + mask = np.array(mask) + mask[mask < 240] = 0 + mask[mask >= 240] = 1 + + # transform + I1 = self.image_transform(I1) + I2 = self.image_transform(I2) + I_reference_video = self.image_transform(self.CenterPad(I_reference_video)) + I_reference_video_real = self.image_transform(self.CenterPad(I_reference_video_real)) + flow_forward = self.ToTensor(self.CenterCrop(flow_forward)) + flow_backward = self.ToTensor(self.CenterCrop(flow_backward)) + mask = self.ToTensor(self.CenterCrop(mask)) + + if np.random.random() < self.real_reference_probability: + I_reference_output = I_reference_video_real + placeholder = torch.zeros_like(I1) + self_ref_flag = torch.zeros_like(I1) + else: + I_reference_output = I_reference_video + placeholder = I2 if np.random.random() < self.nonzero_placeholder_probability else torch.zeros_like(I1) + self_ref_flag = torch.ones_like(I1) + + outputs = [ + I1, + I2, + I_reference_output, + flow_forward, + flow_backward, + mask, + placeholder, + self_ref_flag, + ] + + except Exception as e: + print("problem in, ", path) + print(e) + return self.__getitem__(np.random.randint(0, len(self.image_pairs))) + return outputs + + def __len__(self): + return len(self.image_pairs) + + +def batch_lab2rgb_transpose_mc(img_l_mc, img_ab_mc, nrow=8): + if isinstance(img_l_mc, Variable): + img_l_mc = img_l_mc.data.cpu() + if isinstance(img_ab_mc, Variable): + img_ab_mc = img_ab_mc.data.cpu() + + if img_l_mc.is_cuda: + img_l_mc = img_l_mc.cpu() + if img_ab_mc.is_cuda: + img_ab_mc = img_ab_mc.cpu() + + assert img_l_mc.dim() == 4 and img_ab_mc.dim() == 4, "only for batch input" + + l_norm, ab_norm = 1.0, 1.0 + l_mean, ab_mean = 50.0, 0 + img_l = img_l_mc * l_norm + l_mean + img_ab = img_ab_mc * ab_norm + ab_mean + pred_lab = torch.cat((img_l, img_ab), dim=1) + grid_lab = vutils.make_grid(pred_lab, nrow=nrow).numpy().astype("float64") + return (np.clip(color.lab2rgb(grid_lab.transpose((1, 2, 0))), 0, 1) * 255).astype("uint8") diff --git a/lib/videoloader_imagenet.py b/lib/videoloader_imagenet.py new file mode 100644 index 0000000..e7eb3c2 --- /dev/null +++ b/lib/videoloader_imagenet.py @@ -0,0 +1,290 @@ +import os +import os.path as osp +import struct + +import cv2 +import numpy as np +import torch +import torch.utils.data as data +import torchvision.transforms as transforms +from PIL import Image +from scipy.ndimage import map_coordinates +from scipy.ndimage.filters import gaussian_filter +from utils.util_distortion import CenterPadCrop_numpy, Distortion_with_flow, Normalize, RGB2Lab, ToTensor + +cv2.setNumThreads(0) + + +def parse_images(dir, with_bad, with_mid): + print("dir is: ", dir) + dir = osp.expanduser(dir) + + image_pairs = [] + + no_mid_cnt, no_bad_cnt = 0, 0 + for target in sorted(os.listdir(dir)): + d = osp.join(dir, target) + if not osp.isdir(d): + continue + + pair_file = osp.join(d, "pairs.txt") + + if osp.exists(pair_file): + with open(pair_file, "r") as f: + lines = f.readlines() + for line in lines: + pair = line.strip().split(" ") + name0, postfix = pair[0].split(".") + name1, postfix = pair[1].split(".") + if float(pair[2]) > 0: + item0 = (dir, target, name0, name1, 2) + item1 = (dir, target, name1, name0, 2) + image_pairs.append(item0) + image_pairs.append(item1) + + else: + raise (RuntimeError("Found no pair.txt in subfolders of: " + d + "\n")) + + if with_mid: + pair_file = osp.join(d, "pairs_mid.txt") + + if osp.exists(pair_file): + with open(pair_file, "r") as f: + for line in f: + pair = line.strip().split(" ") + name0, postfix = pair[0].split(".") + name1, postfix = pair[1].split(".") + item0 = (dir, target, name0, name1, 0) + item1 = (dir, target, name1, name0, 0) + image_pairs.append(item0) + image_pairs.append(item1) + + else: + no_mid_cnt += 1 + + if with_bad: + pair_file = osp.join(d, "pairs_bad.txt") + + if osp.exists(pair_file): + with open(pair_file, "r") as f: + for line in f: + pair = line.strip().split(" ") + name0, postfix = pair[0].split(".") + name1, postfix = pair[1].split(".") + item0 = (dir, target, name0, name1, -1) + item1 = (dir, target, name1, name0, -1) + image_pairs.append(item0) + image_pairs.append(item1) + + else: + no_bad_cnt += 1 + + if no_bad_cnt > 0: + print("find no pairs_bad.txt in %d folders for dir: %s" % (no_bad_cnt, dir)) + + if no_mid_cnt > 0: + print("find no pairs_mid.txt in %d folders for dir: %s" % (no_mid_cnt, dir)) + + return image_pairs + + +def pil_loader(path): + with open(path, "rb") as f: + with Image.open(f) as img: + return img.convert("RGB") + + +def image_loader(path): + return pil_loader(path) + + +def combo5_loader(path, real_w, real_h): + with open(path, "rb") as f: + d = f.read(4) + im_sz = struct.unpack("i", d) + h = im_sz[0] + + d = f.read(4) + im_sz = struct.unpack("i", d) + w = im_sz[0] + + d = f.read(4) + im_sz = struct.unpack("i", d) + d = f.read(im_sz[0]) + file_bytes = np.asarray(bytearray(d), dtype=np.uint8) + img_data_ndarray = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) + img_data_ndarray = cv2.cvtColor(img_data_ndarray, cv2.COLOR_BGR2RGB) + warp_ba = Image.fromarray(img_data_ndarray) + img_data_ndarray = img_data_ndarray * 0 + warp_aba = Image.fromarray(img_data_ndarray) + + errs = [] + empty_array = np.zeros([np.array(warp_ba).shape[0], np.array(warp_ba).shape[1]]) + for _ in range(5): + err_ba = Image.fromarray(empty_array) + err_ab = Image.fromarray(empty_array) + errs.append([err_ba, err_ab]) + return errs, warp_ba, warp_aba + + +class VideosDataset_ImageNet(data.Dataset): + def __init__( + self, + data_root, + epoch, + image_size, + with_bad=False, + with_mid=False, + transforms_imagenet=None, + distortion_level=3, + brightnessjitter=0, + nonzero_placeholder_probability=0.5, + extra_reference_transform=None, + real_reference_probability=1, + ): + image_pairs = [] + curr_image_pairs = parse_images(data_root, with_bad, with_mid) + image_pairs += curr_image_pairs + print("##### parsing image_a pairs in %s: %d pairs #####" % (data_root, len(curr_image_pairs))) + if not image_pairs: + raise RuntimeError("Found 0 image_a pairs in all the data_roots") + + self.image_pairs = image_pairs + self.transforms_imagenet_raw = transforms_imagenet + self.extra_reference_transform = transforms.Compose(extra_reference_transform) + self.real_reference_probability = real_reference_probability + self.transforms_imagenet = transforms.Compose(transforms_imagenet) + self.epoch = epoch + self.image_size = image_size + self.real_len = len(self.image_pairs) + self.image_pairs *= epoch + self.distortion_level = distortion_level + self.distortion_transform = Distortion_with_flow() + self.brightnessjitter = brightnessjitter + self.flow_transform = transforms.Compose([CenterPadCrop_numpy(self.image_size), ToTensor()]) + self.nonzero_placeholder_probability = nonzero_placeholder_probability + self.ToTensor = ToTensor() + self.Normalize = Normalize() + + def __getitem__(self, index): + try: + image_id = 0 + pair_id = index + + combo_path = None + image_a_path = None + image_b_path = None + + image_names = ["", ""] + dir_root, cls_dir, image_names[0], image_names[1], is_good = self.image_pairs[pair_id] + sub_dir = osp.join(dir_root, cls_dir) + if is_good >= 1: + image_a_path = osp.join(sub_dir, "input", "%s.JPEG" % image_names[image_id]) + image_b_path = osp.join(sub_dir, "input", "%s.JPEG" % image_names[1 - image_id]) + elif is_good == 0: + image_a_path = osp.join(sub_dir, "input_mid", "%s.JPEG" % image_names[image_id]) + image_b_path = osp.join(sub_dir, "input_mid", "%s.JPEG" % image_names[1 - image_id]) + else: + image_a_path = osp.join(sub_dir, "input_bad", "%s.JPEG" % image_names[image_id]) + image_b_path = osp.join(sub_dir, "input_bad", "%s.JPEG" % image_names[1 - image_id]) + + if np.random.random() > 0.5: + image_a_path, image_b_path = image_b_path, image_a_path + + I1 = image_loader(image_a_path) + I2 = I1 + I_reference_video = I1 + I_reference_video_real = image_loader(image_b_path) + + ## generate the flow + height, width = np.array(I2).shape[0], np.array(I2).shape[1] + alpha = np.random.rand() * self.distortion_level + distortion_range = 50 + random_state = np.random.RandomState(None) + shape = self.image_size[0], self.image_size[1] + # dx: flow on the vertical direction; dy: flow on the horizontal direction + forward_dx = ( + gaussian_filter((random_state.rand(*shape) * 2 - 1), distortion_range, mode="constant", cval=0) + * alpha + * 1000 + ) + forward_dy = ( + gaussian_filter((random_state.rand(*shape) * 2 - 1), distortion_range, mode="constant", cval=0) + * alpha + * 1000 + ) + + for transform in self.transforms_imagenet_raw: + if type(transform) is RGB2Lab: + I1_raw = I1 + I1 = transform(I1) + for transform in self.transforms_imagenet_raw: + if type(transform) is RGB2Lab: + I2 = self.distortion_transform(I2, forward_dx, forward_dy) + I2_raw = I2 + I2 = transform(I2) + I2[0:1, :, :] = I2[0:1, :, :] + torch.randn(1) * self.brightnessjitter + + I_reference_video = self.extra_reference_transform(I_reference_video) + for transform in self.transforms_imagenet_raw: + I_reference_video = transform(I_reference_video) + + I_reference_video_real = self.transforms_imagenet(I_reference_video_real) + + flow_forward_raw = np.stack((forward_dy, forward_dx), axis=-1) + flow_backward_raw = np.zeros_like(flow_forward_raw) + flow_forward = self.flow_transform(flow_forward_raw) + flow_backward = self.flow_transform(flow_backward_raw) + + # update the mask for the pixels on the border + grid_x, grid_y = np.meshgrid(np.arange(self.image_size[0]), np.arange(self.image_size[1]), indexing="ij") + grid = np.stack((grid_y, grid_x), axis=-1) + grid_warp = grid + flow_forward_raw + location_y = grid_warp[:, :, 0].flatten() + location_x = grid_warp[:, :, 1].flatten() + I2_raw = np.array(I2_raw).astype(float) + I21_r = map_coordinates(I2_raw[:, :, 0], np.stack((location_x, location_y)), cval=-1).reshape( + (self.image_size[0], self.image_size[1]) + ) + I21_g = map_coordinates(I2_raw[:, :, 1], np.stack((location_x, location_y)), cval=-1).reshape( + (self.image_size[0], self.image_size[1]) + ) + I21_b = map_coordinates(I2_raw[:, :, 2], np.stack((location_x, location_y)), cval=-1).reshape( + (self.image_size[0], self.image_size[1]) + ) + I21_raw = np.stack((I21_r, I21_g, I21_b), axis=2) + mask = np.ones((self.image_size[0], self.image_size[1])) + mask[(I21_raw[:, :, 0] == -1) & (I21_raw[:, :, 1] == -1) & (I21_raw[:, :, 2] == -1)] = 0 + mask[abs(I21_raw - I1_raw).sum(axis=-1) > 50] = 0 + mask = self.ToTensor(mask) + + if np.random.random() < self.real_reference_probability: + I_reference_output = I_reference_video_real + placeholder = torch.zeros_like(I1) + self_ref_flag = torch.zeros_like(I1) + else: + I_reference_output = I_reference_video + placeholder = I2 if np.random.random() < self.nonzero_placeholder_probability else torch.zeros_like(I1) + self_ref_flag = torch.ones_like(I1) + + outputs = [ + I1, + I2, + I_reference_output, + flow_forward, + flow_backward, + mask, + placeholder, + self_ref_flag, + ] + + except Exception as e: + if combo_path is not None: + print("problem in ", combo_path) + print("problem in, ", image_a_path) + print(e) + return self.__getitem__(np.random.randint(0, len(self.image_pairs))) + return outputs + + def __len__(self): + return len(self.image_pairs) diff --git a/models/ContextualLoss.py b/models/ContextualLoss.py new file mode 100644 index 0000000..a37a8c6 --- /dev/null +++ b/models/ContextualLoss.py @@ -0,0 +1,303 @@ +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision +from utils.util import feature_normalize + +postpa = torchvision.transforms.Compose( + [ + torchvision.transforms.Lambda(lambda x: x.mul_(1.0 / 255)), + torchvision.transforms.Normalize( + mean=[-0.40760392, -0.45795686, -0.48501961], std=[1, 1, 1] # add imagenet mean + ), + torchvision.transforms.Lambda(lambda x: x[torch.LongTensor([2, 1, 0])]), # turn to RGB + ] +) +postpb = torchvision.transforms.Compose([torchvision.transforms.ToPILImage()]) + + +def post_processing(tensor): + t = postpa(tensor) # denormalize the image since the optimized tensor is the normalized one + t[t > 1] = 1 + t[t < 0] = 0 + img = postpb(t) + img = np.array(img) + return img + + +class ContextualLoss(nn.Module): + """ + input is Al, Bl, channel = 1, range ~ [0, 255] + """ + + def __init__(self): + super(ContextualLoss, self).__init__() + return None + + def forward(self, X_features, Y_features, h=0.1, feature_centering=True): + """ + X_features&Y_features are are feature vectors or feature 2d array + h: bandwidth + return the per-sample loss + """ + batch_size = X_features.shape[0] + feature_depth = X_features.shape[1] + feature_size = X_features.shape[2] + + # to normalized feature vectors + if feature_centering: + X_features = X_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + Y_features = Y_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + X_features = feature_normalize(X_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + Y_features = feature_normalize(Y_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + + # conine distance = 1 - similarity + X_features_permute = X_features.permute(0, 2, 1) # batch_size * feature_size^2 * feature_depth + d = 1 - torch.matmul(X_features_permute, Y_features) # batch_size * feature_size^2 * feature_size^2 + + # normalized distance: dij_bar + d_norm = d / (torch.min(d, dim=-1, keepdim=True)[0] + 1e-5) # batch_size * feature_size^2 * feature_size^2 + + # pairwise affinity + w = torch.exp((1 - d_norm) / h) + A_ij = w / torch.sum(w, dim=-1, keepdim=True) + + # contextual loss per sample + CX = torch.mean(torch.max(A_ij, dim=1)[0], dim=-1) + return -torch.log(CX) + + +class ContextualLoss_forward(nn.Module): + """ + input is Al, Bl, channel = 1, range ~ [0, 255] + """ + + def __init__(self): + super(ContextualLoss_forward, self).__init__() + return None + + def forward(self, X_features, Y_features, h=0.1, feature_centering=True): + """ + X_features&Y_features are are feature vectors or feature 2d array + h: bandwidth + return the per-sample loss + """ + batch_size = X_features.shape[0] + feature_depth = X_features.shape[1] + feature_size = X_features.shape[2] + + # to normalized feature vectors + if feature_centering: + X_features = X_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + Y_features = Y_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + X_features = feature_normalize(X_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + Y_features = feature_normalize(Y_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + + # conine distance = 1 - similarity + X_features_permute = X_features.permute(0, 2, 1) # batch_size * feature_size^2 * feature_depth + d = 1 - torch.matmul(X_features_permute, Y_features) # batch_size * feature_size^2 * feature_size^2 + + # normalized distance: dij_bar + d_norm = d / (torch.min(d, dim=-1, keepdim=True)[0] + 1e-5) # batch_size * feature_size^2 * feature_size^2 + + # pairwise affinity + w = torch.exp((1 - d_norm) / h) + A_ij = w / torch.sum(w, dim=-1, keepdim=True) + + # contextual loss per sample + CX = torch.mean(torch.max(A_ij, dim=-1)[0], dim=1) + return -torch.log(CX) + + +class ContextualLoss_complex(nn.Module): + """ + input is Al, Bl, channel = 1, range ~ [0, 255] + """ + + def __init__(self): + super(ContextualLoss_complex, self).__init__() + return None + + def forward(self, X_features, Y_features, h=0.1, patch_size=1, direction="forward"): + """ + X_features&Y_features are are feature vectors or feature 2d array + h: bandwidth + return the per-sample loss + """ + batch_size = X_features.shape[0] + feature_depth = X_features.shape[1] + feature_size = X_features.shape[2] + + # to normalized feature vectors + X_features = X_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + Y_features = Y_features - Y_features.view(batch_size, feature_depth, -1).mean(dim=-1).unsqueeze( + dim=-1 + ).unsqueeze(dim=-1) + X_features = feature_normalize(X_features) # batch_size * feature_depth * feature_size^2 + Y_features = feature_normalize(Y_features) # batch_size * feature_depth * feature_size^2 + + # to normalized feature vectors + X_features = F.unfold( + X_features, kernel_size=(patch_size, patch_size), stride=(1, 1), padding=(patch_size // 2, patch_size // 2) + ) # batch_size * feature_depth_new * feature_size^2 + Y_features = F.unfold( + Y_features, kernel_size=(patch_size, patch_size), stride=(1, 1), padding=(patch_size // 2, patch_size // 2) + ) # batch_size * feature_depth_new * feature_size^2 + + # conine distance = 1 - similarity + X_features_permute = X_features.permute(0, 2, 1) # batch_size * feature_size^2 * feature_depth + d = 1 - torch.matmul(X_features_permute, Y_features) # batch_size * feature_size^2 * feature_size^2 + + # normalized distance: dij_bar + d_norm = d / (torch.min(d, dim=-1, keepdim=True)[0] + 1e-5) # batch_size * feature_size^2 * feature_size^2 + + # pairwise affinity + w = torch.exp((1 - d_norm) / h) + A_ij = w / torch.sum(w, dim=-1, keepdim=True) + + # contextual loss per sample + if direction == "forward": + CX = torch.mean(torch.max(A_ij, dim=-1)[0], dim=1) + else: + CX = torch.mean(torch.max(A_ij, dim=1)[0], dim=-1) + + return -torch.log(CX) + + +class ChamferDistance_patch_loss(nn.Module): + """ + input is Al, Bl, channel = 1, range ~ [0, 255] + """ + + def __init__(self): + super(ChamferDistance_patch_loss, self).__init__() + return None + + def forward(self, X_features, Y_features, patch_size=3, image_x=None, image_y=None, h=0.1, Y_features_in=None): + """ + X_features&Y_features are are feature vectors or feature 2d array + h: bandwidth + return the per-sample loss + """ + batch_size = X_features.shape[0] + feature_depth = X_features.shape[1] + feature_size = X_features.shape[2] + + # to normalized feature vectors + X_features = F.unfold( + X_features, kernel_size=(patch_size, patch_size), stride=(1, 1), padding=(patch_size // 2, patch_size // 2) + ) # batch_size, feature_depth_new * feature_size^2 + Y_features = F.unfold( + Y_features, kernel_size=(patch_size, patch_size), stride=(1, 1), padding=(patch_size // 2, patch_size // 2) + ) # batch_size, feature_depth_new * feature_size^2 + + if image_x is not None and image_y is not None: + image_x = torch.nn.functional.interpolate(image_x, size=(feature_size, feature_size), mode="bilinear").view( + batch_size, 3, -1 + ) + image_y = torch.nn.functional.interpolate(image_y, size=(feature_size, feature_size), mode="bilinear").view( + batch_size, 3, -1 + ) + + X_features_permute = X_features.permute(0, 2, 1) # batch_size * feature_size^2 * feature_depth + similarity_matrix = torch.matmul(X_features_permute, Y_features) # batch_size * feature_size^2 * feature_size^2 + NN_index = similarity_matrix.max(dim=-1, keepdim=True)[1].squeeze() + + if Y_features_in is not None: + loss = torch.mean((X_features - Y_features_in.detach()) ** 2) + Y_features_in = Y_features_in.detach() + else: + loss = torch.mean((X_features - Y_features[:, :, NN_index].detach()) ** 2) + Y_features_in = Y_features[:, :, NN_index].detach() + + # re-arrange image + if image_x is not None and image_y is not None: + image_y_rearrange = image_y[:, :, NN_index] + image_y_rearrange = image_y_rearrange.view(batch_size, 3, feature_size, feature_size) + image_x = image_x.view(batch_size, 3, feature_size, feature_size) + image_y = image_y.view(batch_size, 3, feature_size, feature_size) + + return loss + + +class ChamferDistance_loss(nn.Module): + """ + input is Al, Bl, channel = 1, range ~ [0, 255] + """ + + def __init__(self): + super(ChamferDistance_loss, self).__init__() + return None + + def forward(self, X_features, Y_features, image_x, image_y, h=0.1, Y_features_in=None): + """ + X_features&Y_features are are feature vectors or feature 2d array + h: bandwidth + return the per-sample loss + """ + batch_size = X_features.shape[0] + feature_depth = X_features.shape[1] + feature_size = X_features.shape[2] + + # to normalized feature vectors + X_features = feature_normalize(X_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + Y_features = feature_normalize(Y_features).view( + batch_size, feature_depth, -1 + ) # batch_size * feature_depth * feature_size^2 + image_x = torch.nn.functional.interpolate(image_x, size=(feature_size, feature_size), mode="bilinear").view( + batch_size, 3, -1 + ) + image_y = torch.nn.functional.interpolate(image_y, size=(feature_size, feature_size), mode="bilinear").view( + batch_size, 3, -1 + ) + + X_features_permute = X_features.permute(0, 2, 1) # batch_size * feature_size^2 * feature_depth + similarity_matrix = torch.matmul(X_features_permute, Y_features) # batch_size * feature_size^2 * feature_size^2 + NN_index = similarity_matrix.max(dim=-1, keepdim=True)[1].squeeze() + if Y_features_in is not None: + loss = torch.mean((X_features - Y_features_in.detach()) ** 2) + Y_features_in = Y_features_in.detach() + else: + loss = torch.mean((X_features - Y_features[:, :, NN_index].detach()) ** 2) + Y_features_in = Y_features[:, :, NN_index].detach() + + # re-arrange image + image_y_rearrange = image_y[:, :, NN_index] + image_y_rearrange = image_y_rearrange.view(batch_size, 3, feature_size, feature_size) + image_x = image_x.view(batch_size, 3, feature_size, feature_size) + image_y = image_y.view(batch_size, 3, feature_size, feature_size) + + return loss, Y_features_in, X_features + + +if __name__ == "__main__": + contextual_loss = ContextualLoss() + batch_size = 32 + feature_depth = 8 + feature_size = 16 + X_features = torch.zeros(batch_size, feature_depth, feature_size, feature_size) + Y_features = torch.zeros(batch_size, feature_depth, feature_size, feature_size) + + cx_loss = contextual_loss(X_features, Y_features, 1) + print(cx_loss) diff --git a/models/GAN_models.py b/models/GAN_models.py index a3b2f3d..3dddc07 100644 --- a/models/GAN_models.py +++ b/models/GAN_models.py @@ -101,61 +101,13 @@ class Self_Attention(nn.Module): return out -class Discriminator_WITHOUT_FC_x64_video(nn.Module): +class Discriminator_x64(nn.Module): """ Discriminative Network """ def __init__(self, in_size=6, ndf=64): - super(Discriminator_WITHOUT_FC_x64_video, self).__init__() - self.in_size = in_size - self.ndf = ndf - - self.layer1 = nn.Sequential( - # input size is in_size x 64 x 64 - SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), - nn.LeakyReLU(0.2, inplace=True), - ) - self.layer2 = nn.Sequential( - # state size: ndf x 32 x 32 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 2), - nn.LeakyReLU(0.2, inplace=True), - ) - self.layer3 = nn.Sequential( - # state size: (ndf * 2) x 16 x 16 - SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 4), - nn.LeakyReLU(0.2, inplace=True), - ) - self.layer4 = nn.Sequential( - # state size: (ndf * 4) x 8 x 8 - # Self_Attention(self.ndf * 4, 'relu'), - SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 8), - nn.LeakyReLU(0.2, inplace=True), - ) - - self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, [3, 6], 1, 0)) - - def forward(self, input): - feature1 = self.layer1(input) - feature2 = self.layer2(feature1) - feature3 = self.layer3(feature2) - feature4 = self.layer4(feature3) - output = self.last(feature4) - output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) - - return output, feature4 - - -class Discriminator_WITHOUT_FC_x64_video_BIG(nn.Module): - """ - Discriminative Network - """ - - def __init__(self, in_size=6, ndf=64): - super(Discriminator_WITHOUT_FC_x64_video_BIG, self).__init__() + super(Discriminator_x64, self).__init__() self.in_size = in_size self.ndf = ndf @@ -205,189 +157,237 @@ class Discriminator_WITHOUT_FC_x64_video_BIG(nn.Module): return output, feature4 -class Discriminator_WITHOUT_FC_x64(nn.Module): - """ - Discriminative Network - """ +# class Discriminator_WITHOUT_FC_x64_video(nn.Module): +# """ +# Discriminative Network +# """ - def __init__(self, in_size=3, ndf=64): - super(Discriminator_WITHOUT_FC_x64, self).__init__() - self.in_size = in_size - self.ndf = ndf +# def __init__(self, in_size=6, ndf=64): +# super(Discriminator_WITHOUT_FC_x64_video, self).__init__() +# self.in_size = in_size +# self.ndf = ndf - self.main = nn.Sequential( - # input size is in_size x 64 x 64 - SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), - nn.LeakyReLU(0.2, inplace=True), - # state size: ndf x 32 x 32 - Self_Attention(self.ndf), - SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 2), - nn.LeakyReLU(0.2, inplace=True), - # state size: (ndf * 2) x 16 x 16 - SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 4), - nn.LeakyReLU(0.2, inplace=True), - # state size: (ndf * 4) x 8 x 8 - SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 8), - nn.LeakyReLU(0.2, inplace=True), - # sate size: (ndf * 8) x 4 x 4 - ) +# self.layer1 = nn.Sequential( +# # input size is in_size x 64 x 64 +# SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), +# nn.LeakyReLU(0.2, inplace=True), +# ) +# self.layer2 = nn.Sequential( +# # state size: ndf x 32 x 32 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 2), +# nn.LeakyReLU(0.2, inplace=True), +# ) +# self.layer3 = nn.Sequential( +# # state size: (ndf * 2) x 16 x 16 +# SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 4), +# nn.LeakyReLU(0.2, inplace=True), +# ) +# self.layer4 = nn.Sequential( +# # state size: (ndf * 4) x 8 x 8 +# # Self_Attention(self.ndf * 4, 'relu'), +# SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 8), +# nn.LeakyReLU(0.2, inplace=True), +# ) - self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) +# self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, [3, 6], 1, 0)) - def forward(self, input): +# def forward(self, input): +# feature1 = self.layer1(input) +# feature2 = self.layer2(feature1) +# feature3 = self.layer3(feature2) +# feature4 = self.layer4(feature3) +# output = self.last(feature4) +# output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) - feature = self.main(input) - output = self.last(feature) - output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) - - return output, feature +# return output, feature4 -class Discriminator_WITHOUT_FC_x64_BIG(nn.Module): - """ - Discriminative Network - """ +# class Discriminator_WITHOUT_FC_x64(nn.Module): +# """ +# Discriminative Network +# """ - def __init__(self, in_size=3, ndf=64): - super(Discriminator_WITHOUT_FC_x64_BIG, self).__init__() - self.in_size = in_size - self.ndf = ndf +# def __init__(self, in_size=3, ndf=64): +# super(Discriminator_WITHOUT_FC_x64, self).__init__() +# self.in_size = in_size +# self.ndf = ndf - self.main = nn.Sequential( - # 256 X 256 - SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), - nn.LeakyReLU(0.2, inplace=True), - # 128 X 128 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf), - nn.LeakyReLU(0.2, inplace=True), - # 64 X 64 - Self_Attention(self.ndf), - SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 2), - nn.LeakyReLU(0.2, inplace=True), - # 32 X 32 - SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 4), - nn.LeakyReLU(0.2, inplace=True), - # 16 X 16 - SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 8), - nn.LeakyReLU(0.2, inplace=True), - # 8 X 8 - SpectralNorm(nn.Conv2d(self.ndf * 8, self.ndf * 16, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 16), - nn.LeakyReLU(0.2, inplace=True), - ) - # 4 X 4 - self.last = SpectralNorm(nn.Conv2d(self.ndf * 16, 1, 4, 1, 0)) +# self.main = nn.Sequential( +# # input size is in_size x 64 x 64 +# SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: ndf x 32 x 32 +# Self_Attention(self.ndf), +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 2), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: (ndf * 2) x 16 x 16 +# SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 4), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: (ndf * 4) x 8 x 8 +# SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 8), +# nn.LeakyReLU(0.2, inplace=True), +# # sate size: (ndf * 8) x 4 x 4 +# ) - def forward(self, input): +# self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) - feature = self.main(input) - output = self.last(feature) - output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) +# def forward(self, input): - return output, feature +# feature = self.main(input) +# output = self.last(feature) +# output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) + +# return output, feature -class Discriminator_WITHOUT_FC_x128(nn.Module): - """ - Discriminative Network - """ +# class Discriminator_WITHOUT_FC_x64_BIG(nn.Module): +# """ +# Discriminative Network +# """ - def __init__(self, in_size=3, ndf=64): - super(Discriminator_WITHOUT_FC_x128, self).__init__() - self.in_size = in_size - self.ndf = ndf +# def __init__(self, in_size=3, ndf=64): +# super(Discriminator_WITHOUT_FC_x64_BIG, self).__init__() +# self.in_size = in_size +# self.ndf = ndf - self.main = nn.Sequential( - # input size is in_size x 128 x 128 - SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), - nn.LeakyReLU(0.2, inplace=True), - # state size: ndf x 64 x 64 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf), - nn.LeakyReLU(0.2, inplace=True), - # state size: ndf x 32 x 32 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 2), - nn.LeakyReLU(0.2, inplace=True), - # state size: (ndf * 24) x 16 x 16 - SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 4), - nn.LeakyReLU(0.2, inplace=True), - # sate size: (ndf * 4) x 8 x 8 - SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 8), - nn.LeakyReLU(0.2, inplace=True), - # sate size: (ndf * 8) x 4 x 4 - ) +# self.main = nn.Sequential( +# # 256 X 256 +# SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), +# nn.LeakyReLU(0.2, inplace=True), +# # 128 X 128 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf), +# nn.LeakyReLU(0.2, inplace=True), +# # 64 X 64 +# Self_Attention(self.ndf), +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 2), +# nn.LeakyReLU(0.2, inplace=True), +# # 32 X 32 +# SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 4), +# nn.LeakyReLU(0.2, inplace=True), +# # 16 X 16 +# SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 8), +# nn.LeakyReLU(0.2, inplace=True), +# # 8 X 8 +# SpectralNorm(nn.Conv2d(self.ndf * 8, self.ndf * 16, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 16), +# nn.LeakyReLU(0.2, inplace=True), +# ) +# # 4 X 4 +# self.last = SpectralNorm(nn.Conv2d(self.ndf * 16, 1, 4, 1, 0)) - self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) +# def forward(self, input): - def forward(self, input): +# feature = self.main(input) +# output = self.last(feature) +# output = F.avg_pool2d(output, output.size()[2:]).view(output.size()[0], -1) - feature = self.main(input) - output = self.last(feature) - - return output, feature +# return output, feature -class Discriminator_WITHOUT_FC_x256(nn.Module): - """ - Discriminative Network - """ +# class Discriminator_WITHOUT_FC_x128(nn.Module): +# """ +# Discriminative Network +# """ - def __init__(self, in_size=3, ndf=64): - super(Discriminator_WITHOUT_FC_x256, self).__init__() - self.in_size = in_size - self.ndf = ndf +# def __init__(self, in_size=3, ndf=64): +# super(Discriminator_WITHOUT_FC_x128, self).__init__() +# self.in_size = in_size +# self.ndf = ndf - self.main = nn.Sequential( - # input size is in_size x 256 x 256 - SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), - nn.LeakyReLU(0.2, inplace=True), - # state size: ndf x 128 x 128 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf), - nn.LeakyReLU(0.2, inplace=True), - # state size: ndf x 64 x 64 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf), - nn.LeakyReLU(0.2, inplace=True), - # state size: (ndf * 1) x 32 x 32 - SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 2), - nn.LeakyReLU(0.2, inplace=True), - # state size: (ndf * 2) x 16 x 16 - SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 4), - nn.LeakyReLU(0.2, inplace=True), - # sate size: (ndf * 4) x 8 x 8 - SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), - nn.InstanceNorm2d(self.ndf * 8), - nn.LeakyReLU(0.2, inplace=True), - # sate size: (ndf * 8) x 4 x 4 - ) +# self.main = nn.Sequential( +# # input size is in_size x 128 x 128 +# SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: ndf x 64 x 64 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: ndf x 32 x 32 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 2), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: (ndf * 24) x 16 x 16 +# SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 4), +# nn.LeakyReLU(0.2, inplace=True), +# # sate size: (ndf * 4) x 8 x 8 +# SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 8), +# nn.LeakyReLU(0.2, inplace=True), +# # sate size: (ndf * 8) x 4 x 4 +# ) - self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) +# self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) - def forward(self, input): +# def forward(self, input): - feature = self.main(input) - output = self.last(feature) +# feature = self.main(input) +# output = self.last(feature) - return output, feature +# return output, feature -if __name__ == "__main__": - discriminator1 = Discriminator_WITHOUT_FC_x64() - discriminator2 = Discriminator_WITHOUT_FC_x128() - discriminator3 = Discriminator_WITHOUT_FC_x256() - print(discriminator1) - print(discriminator2) - print(discriminator3) +# class Discriminator_WITHOUT_FC_x256(nn.Module): +# """ +# Discriminative Network +# """ + +# def __init__(self, in_size=3, ndf=64): +# super(Discriminator_WITHOUT_FC_x256, self).__init__() +# self.in_size = in_size +# self.ndf = ndf + +# self.main = nn.Sequential( +# # input size is in_size x 256 x 256 +# SpectralNorm(nn.Conv2d(self.in_size, self.ndf, 4, 2, 1)), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: ndf x 128 x 128 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: ndf x 64 x 64 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: (ndf * 1) x 32 x 32 +# SpectralNorm(nn.Conv2d(self.ndf, self.ndf * 2, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 2), +# nn.LeakyReLU(0.2, inplace=True), +# # state size: (ndf * 2) x 16 x 16 +# SpectralNorm(nn.Conv2d(self.ndf * 2, self.ndf * 4, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 4), +# nn.LeakyReLU(0.2, inplace=True), +# # sate size: (ndf * 4) x 8 x 8 +# SpectralNorm(nn.Conv2d(self.ndf * 4, self.ndf * 8, 4, 2, 1)), +# nn.InstanceNorm2d(self.ndf * 8), +# nn.LeakyReLU(0.2, inplace=True), +# # sate size: (ndf * 8) x 4 x 4 +# ) + +# self.last = SpectralNorm(nn.Conv2d(self.ndf * 8, 1, 4, 1, 0)) + +# def forward(self, input): + +# feature = self.main(input) +# output = self.last(feature) + +# return output, feature + + +# if __name__ == "__main__": +# discriminator1 = Discriminator_WITHOUT_FC_x64() +# discriminator2 = Discriminator_WITHOUT_FC_x128() +# discriminator3 = Discriminator_WITHOUT_FC_x256() +# print(discriminator1) +# print(discriminator2) +# print(discriminator3) diff --git a/models/NonlocalNet.py b/models/NonlocalNet.py index 1713db0..6683d1a 100644 --- a/models/NonlocalNet.py +++ b/models/NonlocalNet.py @@ -189,13 +189,13 @@ class Self_Attn(nn.Module): return out -class VGG19_feature_color_torchversion(nn.Module): +class VGG19_pytorch(nn.Module): """ NOTE: no need to pre-process the input; input tensor should range in [0,1] """ def __init__(self, pool="max"): - super(VGG19_feature_color_torchversion, self).__init__() + super(VGG19_pytorch, self).__init__() self.conv1_1 = nn.Conv2d(3, 64, kernel_size=3, padding=1) self.conv1_2 = nn.Conv2d(64, 64, kernel_size=3, padding=1) self.conv2_1 = nn.Conv2d(64, 128, kernel_size=3, padding=1) diff --git a/opt.txt b/opt.txt new file mode 100644 index 0000000..3d980bc --- /dev/null +++ b/opt.txt @@ -0,0 +1,37 @@ +train.py 2020-12-21 15:03:17 +------------ Options ------------- +batch_size: 16 +beta1: 0.5 +checkpoint_dir: checkpoints/default +contextual_loss_direction: forward +data_root: /home/zhanbo/remote/video/video_pair3/ +data_root_imagenet: /home/zhanbo/remote/imagenet_ref_pair/ +domain_invariant: False +epoch: 40 +gpu_ids: 0,1,2,3 +ic: 7 +image_size: [216, 384] +load_pretrained_model: True +lr: 1e-05 +lr_gamma: 0.1 +lr_step: 100 +luminance_noise: 2.0 +nonzero_placeholder_probability: 0.0 +permute_data: True +print_step: 2 +real_reference_probability: 0.7 +resume: False +resume_epoch: 0 +tb_log_step: 50 +weight_consistent: 0.02 +weight_contextual: 0.2 +weight_gan: 0.2 +weight_nonlocal_consistent: 0.0 +weight_nonlocal_smoothness: 0.0 +weight_perceptual: 0.001 +weight_smoothness: 5.0 +weigth_l1: 2.0 +with_bad: True +with_mid: True +workers: 8 +-------------- End ---------------- diff --git a/requirements.txt b/requirements.txt index 7539545..b940bde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,5 @@ prefetch_generator PyYAML tensorboard future -numba \ No newline at end of file +numba +pypng \ No newline at end of file diff --git a/sample_videos/output/v32_01/00000.jpg b/sample_videos/output/v32_01/00000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..49d9a3d3fc63d291568d54ee2c96486a907e54a1 GIT binary patch literal 20749 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 lu3E@=MY8TjYQF)pwphEuY5s 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 MX(#}qiYbWy+4itO8vpd`G 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 BQfdGI literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00003.jpg b/sample_videos/output/v32_01/00003.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8eb6a49c81cd4f4494a040cfe7a8c02c5f0054a1 GIT binary patch literal 21030 zcmbTdcT`hb*F7A15fm^%2t_&xO-h0^l@@wU0|F==5{fiIu+fD`Z$bowkkFDKy(x-H z?}RF1qbVX@3*fchFL>{L-skdzW`8B;Lsrfagn1(fKuS&(o$eaNf`wVWf?g& zc}YpgNvN8p77PYEuB?AbPuoaC2c~`SC2V|rd;*6A!~_M!v}Gk_wf~Qgzxn{eylhPz z01h@e0J|_7hcMe;{ea`F-^s=H`vClNu(5M+a&hzU^6?*Hy`fzQz|O|O!OqFS#l^|V zdiNF9>i|w+E)iKR6YitV-aK*pU5OWe$iuM;$TH3We8LS z2G>2Qr*B|pjTzqm$YTEVm3}SvkVNvnT zl2Q`6nnJCqt*dWnX>Duo=e~9o=Ib|GZ$Eze zyt}vm<vId7RCzq@iw}^=|k9WjT zIqgJVplM!J^Fuy)9pnd)Pvi{0r~>Sj;>Uxf{a)GszlA0JU#;vv3;UmSEdvBN*jN{j zLl|%d@WB8=PBtbJp#l&x5%6Eb?9}mxqV=Mg0bV-t1yk+#vpXvnj~4}A#D)ABFLWj) z5A|%+8h#`S(_9pDEY<|=DzHm)5iD+~Nz5JQHo&+6c4?8(4R?$|!{W6Namp#Y3&ZMY zrF4-&;t+eKO;axfdZ;ZPWs_rbL6)KgvZPuy=rBuE&=jp#JXEOj8|b6FI<{1oX;k17Z8 zh^hmD`5)kGr(*gL;_?egRzjEie0rpGY`VGu=^yUzwN_sYlelbckfHEp$5X);)bpc3 zkFpMz-h|&tTLg@12cH!bS>RN#c#vhLTZb}A-KRgfY&;y%Q?u4$ppf(ocoe+0<-q!w+Ecucf?m0z*KyW!{}3&jggG%yLBAFVM(efS~)&etFApJ7>A>#~WV= z>i8r`zmd1&eCnPI2$FI|wG`Yf!q&qO^oS0a&^+Ntb(?r>e7nf+`9V!2Cg*$OSJX-e&%U{d?KV7lr;d(4%9+#!e>*IHaYXO*U`x9?J9Ez(&HIa7ukB$d z9Vj;+1L|zpItIR+Vvv8m8mrSN$f?d7R4Z&hpp^=AHs%8QWI7qL9*Yx1iir@Zq~c6x z;~@*})%G1Zu2g?h?Y+A`4>aq&&Tn-*Z2m-iQ`u%*y>YzAZ>tM{-3`-Su$vLM6g{+Z zyeQzA`HkC!S1~k&Wc+6Hd8PHw#!qf5O*?XYYM6AHMvf_2H{4Ow=CiGPr=nYQg$s*^ z-u-NcU~IpE#%dVw)4@qgVl}b_U9S_}V*P-GyZZF+$%W^sNX0@Z6t6PuxZQ$a zQ3Gz%V%aq^Wv(|9e69v-*<}RG&+?mRA1)U=e=ywEL@+wNomo^KUL^3Zp&0WOq}& zx_ve`1NC|2TK&5eUgf}5MbE7^xM){?|B25Z5#7iew6|BBjLDB*Fgx3*y9UdM8Q0A& z1b0t@cG?RL7a&LV3S=c&hF>US8 z0GHg}v$kVeV*XBji)@X5h6O9(eUQ`NV;VmRPFlm&M!_Q4Y^aCaC8X{(wG>I85^T?u zaFBY?<|=61eo=~6;{UL6%16?fm`k*8^QbDR2L5nH^aVf$P7^gaBCLs5lptM$GY$ia z0Q>V_wj;;t=!VxGxMKsksn=1|)`B1qrBm;0durHU0ChL*mlx{PkG;P4XpbIztEHbg z0rd(tz<#ac9x_THrI*c=%@FzIz3m=O*db1yhxGwpqFkAsz>*0q(8e1=${PO*8)hy> zqUh(-BMm>!gi^YQxNuJ z4O7^&G;3qxAI@`GcCdtu`ojGhOjU*rw*zF`{r>Tp!6E?Y%VzLJL z%P>AFZV)qjDmnKN5mL!XE+>X+rNUVE zpx;nLb~|2!ham$Knj1s=-hzkePRJD~0T*gH?0v1sI#&2025g$s5%av3khqoT`EB+H zY`-S9o?XW|Z^FhBxDrsWmA~u`AKwCJyRP=0GnvVsoDsTW{iV_Nk%7bK{hMc5w_Cg& zn@{owY6Jy+_bKmY_Z1f>w171SD5~G!UQFLc+7;Gik#ex@wjMdOWiK*v;sS?ihuDI9 zcT_$es*^!g(SH1kYA>Bknjt)Q2{UFoaI;5BIb#cJLwgO=sd}$T_Dii`iL1)e+R=^TIfr4 zi2IsR!*gVH?2N`c2U_aYuDwS8)Y!AiX6m()JDXhr^?rNus50Jvz%FJ8ZXzcT41tY^5Fs@OFpxdKL?w%C0IYFP3@JiPwVVbszH%V@isKsT6fEvTeSOISrh2qBiL z4ut#}@fuPbaGBe5o@4$xh+~>7cH4O_XQ7msmuZAvh}qoSl0V*0koQzhrKc2#mqc$F zGrOM##oPe()QMq$q+cDyy5n_`n|G?dpDOJGiUBx)hXJN)i6%RvoCD%Yz>tVh_>}4I#K03T}f$&-b$s9Pjm2c<_ zUU{)@Rukf}$zlvofg+bu z?7)7E+?Z*3qxwmx7OH6IY`cVWL?*d7L1($-9Y`S(bSg-=V)yaGN!W1J0CN!tUO8W|oZqw1Q1r9kFu} zlzH6rrSa27LRP|S;;S&8Z)QL7T0%(h1=)JGws%PW3jkdb!N$9ido`DMLAEAM?&n)( z$4la*<2_YV;c2E%puOKq8QLqF2-6h602Y#poxT^Z5hrp<(*+tTZFv#QTyX};Tb$;2 z9QHz^B;8q`=A4P-T&>`Su$v%Lha0{(qXwS<`(4T}!m}=SWlS|Qy0@kO0!Z8b6htbP zpq;G<<19|Akrf0wo5)(>@z8V@V!T+giCqc-M3W9;)W0K%1(gHba7jTbolQ(^S*&v7 zAKLj%IYAV1f+&6Y^l=c5lG-s0)hZY49wnBhV%kCyUBu8WGr|xLvNL) zSLq)kIm=rweap(%G|cN%@btZt{_78qORgz(UE;m`eRw*4u_kDy#d44rB;x<@C_x*P z>F34&`589)q(2x zk3|5tOXp8JFEl(2aq`RkU~mLS|5WO)?yw3MG4|y+jLXoNF?&P0? za#)oinaM{jli%n#0GcONOoZhVjfwgCL%nd%UX?$Vh9xR)bKS64X~dn^mB+_%TZgmu&7W;-(7+%41LX12a~=-rVF9Cm z>VbGUNz5BI6{un)53}~B<0q%`HV?e`{+rmh&ArraI|ST=L0Aq53R`)-1$Vci2xDgLyx%v1eNN3&53qV5ss0yU|HbNWyaG|H=_5=^aDX$3Cz2l`&jUn4a5j)i4p;yyQXasT zpLNQH9}RzW%eXS=SxXofUK-)&+R5IY_|>G&zaP(He3F?J`B=jr&*^7u8g~T}w^-6? zH5Uxl=7{auzhldE5$)c;V=yB>Ub}s^Ab>N*#xT!q{CUNY*6P+rVW*Xf`Pa;!Z{Phu zf2YdqH|io3xB8GPt~V9f78-~Ajdl|5@n0K@g-X{b9* z`3Zfo-;(gl`>y>QW|A=4XEXMMV2ZB z6|_n~GWl@;sGvG*njPs2dx!MeED=>-=nOfU=O+3%&%MJJfE1p70svyOjd6o%p$Sna ziTeg~g|6L&Ty%;0NPE|=9yNmnG;^KA`9$IYoG(7O-FyNq=c{-!4$ep52uM5{NQ#M6 z(Zs*}Xvez_cX)Yq>sK11YXVbtb>mkW>8N&=A`qKq!XnncGgM$9_&39HvQVpefLQSV zW>*#r5AX=v{Xo^6E8Px1-rAcjs?{JO?;b+jT1*lk5U=C;kE)wy3e|WW-AXcoR7?U2X^I zIn~~OyUTl9=&&9=uzSYQ_r}L0VcnN{a^1YuB38$q_e%8ZlHbmxAJ^RS;A*owkyMr> zC>$@yaCmIlLez8F*z90x_HqSoJr?GlJ6o8Qubde`uk34>AvM(`Wz4NoP)^+Sm{d(A z;74Z~|J7G^UieJ>lNvbSBy0he*@sabd$TiL4C5_d=U_Q@&&|tS`8UtsLnN67G}X)p zmS&6$7>caNv=XM3u^^k!n7MFw()z2UxBE_&9jAh?&OXlN8XtGd0?QIuzDv70^?QrJ z_gTA#--Pgr(+t4qI+(KX3HM;sRuM3(rau{c#3d!pKY@`OD=@EadTJUhEP|5^7C35O z|JuXqVqGwLGA4!UTtK`WevgqV4ilpgJ?Rw9+DOYa_L8}M*rO<<3^0IbI4cy8e-l^; zKxVfUzSO61J-tfq2)zx!!a%ireU()`Z}o9%HAQbS5NK3lzXp>5S+K@+@bzJwluDA})EanD;TPtjUc%3L~)OlC=a z^KFz0-LFx0E=C$wqgtG3%-o+htaX{KDjz)_3q&A;N@lH1{?2?4z-VSZt^0&440qDz z_4KRMP`5KSb`(PD1XqNEL*dVM?(~Lj3_SidVfDbW?{0pGAm&Vx3eUuH_a*Qa48B3-Y%Lv0+>-%^dq z)bk=lmq_moxwdU5Sr3GnXaW3~>Nxk<5{1hC93ND4HueU-nCY6mUsp4&s(JNKPZJtqI@Hxh0e@giZ)8)xU3ie6WSTNV6H>6&1-QC6s1>Us zJgXBuzNGs;**H!(I)}<4d+Uu%SYRvgDw!6mnW#DnyH5e|EktMqnP>qGVqcL8wvg|m zk3|hLtSs3}GCmej28<3vW7e4f%xZOE1QxJbRp`WhxgA{ZiyDSxL_R7>RR5%3k9Z)Z zGO7$Zd2;~Z^l5*;rkJ0wQVF3yp{%c#Xidq$v9aSKNA~Sy#=)LlF9+nV zYB2=b_@df^1#Mkb>^19-RGkQ0g1xh3D4}fz3t%<*d8RYgw!fA0fK)JY;2YM{qxt-8 zV5y}o-zc$(8-Z=pH*o>bpO96%8uIz?Z)5yy5Wq5&7ODsBAH=?yA7RM9)7Hy4rqQ2G zR88!-rt-=m{zGurOwDq#*k(&z)|U1zF}A=pexpOiy7@B?=mGTgsfO$-G1APSfdFwr ziaSzQj%T?+>9*pJjhe`#Oh&&B|LaDTIRPOIV4WGVn#m^iZB7TjpxlsX8+B=;ezQpf zuAqHf+wM|8y@x#|SiQV3w9v`k;np#V*Ac3>W#dD!N15{_7v^s1nOa`vkMTiT?D`4! z4cOJ<=_Tipru-d8s%T^K!UZOHl9u&^$C+4of#h|$w!BVDD>8j286)0AnOhWp;e1bV zZ8cF0Ts~%@w~=Gw!RT*o>mRur7XFxd_o%O-=hqg?zO9Nm&!F&URvxDx#V2A(7N{GS zBq^eDSFF9WO4pQ=ak2NMv$ejr#KvP+z;-d23fCJANG%;TX2t8%FT66<+V!8Eu(`M~ z!b5y3)2VWAK3%<7i&-PX=JbS&g$^fkx7K)QF{~}A&RvoLxjNdLM>L~ro|Npoc6!9P zuu9HfNjpOHTUs8SI1ZY)SYf0fUABm*)o@xkLG+zy(KUeUy+=5vmR*wBv8yHFY5L|= z4x&x?mT>~VpgIsR3U$QU0GiJ)w0j$2J-bFgG@(aCA7zxI6do8raUFprUK62#1RQfO&1Gdl#-|1?;0SZ~_!~@epY$RlJ!krQtX}fgO{JfDy=|yRu*EPY@ZRIwQo%u*jE-jsw_R`Qb0I6d+)BEa& zM)tn|{dn|yP)u(YI1rnB)Ogrd4^s=6Xj0M|@dD#XO+%V%&YAq{98xmn{(Z-&+!|jSd&$zl$!qXG*qv`n5vbjqsBqQOf#Umghn9QFNvGb19~LwdJ)|ks zunLi@JA4WAxlk%$@pOmIidAeN{LK+BB{4W=_nP9DA#ABg)}*+)e`hyV#bGv|ciS^3 zO|2C^(4U)<_3LdOR%BRbW8V4;h#|CG<#ZL}vFuth+uH50KPfKx?mAC1&fQhfq$#sD zWP#N0sDQNn)W{|8M?4qt(WNqk>g+v9*k~Rlnwh)UJ}(tW;ct%aHbeX9v9jTh8Qg-f zCmhUjUTy~9l9P6Bv-|_#ZpR_)NC--wqAL9LVjV?{CdjPJ{b`H@tmzk0_mej)lb zk#ltH%QnJS#4HmVa(M)&s$86Ci84{72?2}fcZ4nPIuw4BTxdIf*K~;C_h;h%)}0E5 zgJIs8)G}28us3jj^A@fw$%j?qA)2z_kpPZoqM9ur?3a8$^DpG&s>(^XGeRe!>Am~` zjS6t79>2q?Ch}`k*}S-mQYw6CHkW7NDur{r<6YttXyNw}E;W+i1`1Dua2TAov>`U% z4_xe)RErz!?wX9NQ8JPDZ!j=^LWPZRI1gWod0`U;&ib`>1)6BbD&wf6`+%YLidvd1 z?OJ2?k3#VR7S`k^9${Ht_NGJ#I)?c=*?Ocp=40HS62(HUyjQK0@|duUXKl;nK>! zw>5+3SU~LEd&6;sFJnNM50NU1lj;fPUn9zw8cg{pA=yQVE4nj|%L+VQt9Q&-=3@ui z`#C?}O-A0%_km`CoxSGs#N_kPm4xZcb$)*V(P4*}xh;OkmZK!U-DZiv<-=UYTNU;= zeieO7;9#JAD))^zXlIDYfGu9yxROs%agiD@Y_n^t(F3u{Ny7&P1?hE^q<#_!FM&h7D+N`0hY( ztEeBIw<^|+%a97zUuwNj9}u4a<~)U8|LFW0D7spbR=k{4Zert`{ChsDqTq&b9~{mV^4+;4(ST6bH`1!Nj-(iTqv3`OTvTTneC4 z{xMLQL>s6ec-gN+u4l|mN32n4V1I=8_MP9jPZdetQLbbN_G~VI(xM9zNG3K}5TM71 zfRV00+?ZO}VR(IXd9c!2LXh~Dw8X+Yc-Cn{)%t})DQlZA;lWefMZOX`XXQiR)s6Yl$)*6R**nkck)j zanDIdV?C_!JJKvtxMQ=jH_qaf*qC(Dl*B2YLk~`wM3m+2*d3mK!g(&r2xEfrE${du4V{~D9N$Hs+8PsN!f+W$|-T{qjum$p4^NiUD>Y5F3iFevA zG&kN%c0Z}i@O>sN2neWXhtqes9_gJDG-bsA$S#dNzptQ%*PC?vw8yqmL^5gf@`(l& zjzFE`x87d~CSTCd46d*l-%05%8arvM@M`gW*KqBCA{sBwFx6fgH9GEWj&#ZrMCVgo z8ni}+aHVGxZx-8JyiBY){=DosQ1FnN#M8)+Hu52thhQ7m;$uD%51Aju&v-OiyvrSOe{)`yA^T zh8?V{qne+)Y4L4-Kn(3mEv4q(y`Yq8G>6`32!@K z#pK@=WcK`GBIju$qz--%r4NP(ktH`-aQU6*^g*iIfxI?QmeutCO<9FVCy3Z9cZ1+v zI*vJeo0GrTdGX=6*IZgq!LfJm#sx^ zO$?M5ES>$daVCa92~AUpCvI<6#LTh<>DK)+3J=bV%X=?b#TnS{mpyh9X@^OPr=SVCd!gZ6{aKR!36l_&jj6TkdImB?(Z@EZsyY@iUM8|}9sw?ab zB@|L`*$7v~%G4SjW9Fw;r79t!{ zFVa+{@dD(J7&kdaaax1DD36VgcK<~<@?8{8qYG}cb8&)I^T`*`ubC^j@-2N9FaggJ zr?3otmK`+g{X3C9c*v!|+Q0`T=Ksm5zx`KE4PbTN2>#tzlg7fMR6P_AO+X@NOymx- z@528MmQ-NjZ=5FHz|wDpv}37>9m9fhdCT~zLQv&=R*iMvW{0Dy)0nw7_R5sabbjmm zOVc_kVePlhJZC_^ErhTdaT45fku1l^Kh;{IMblqTu9`~Gp%x6{+Ioyp5C6U zocZH)l!UY+Je8EshZ8TFTjQpZSC?ZAyZk0RvOXYuaUOiWSRMvm-VYmy3rK2`qb$?) z#XBg4SogZGM{zXtUH2p=6>js9nLgxZ(Bc3(-)Nq8ty{7`j9S0!pew&j5vug<57~ z=K7JLmE41;39}uN#3{sA8gf3~iFYO;;EqKZ##QK}%Nmd9o4GI zAx-p(ad*_5ZNGW38sA{9Q&Ds^J~h?71Tc@uf%T_nDeBj^4JxvvQ)L?W?@-0EvIVuc zxQT+=<_96_pa#t%Ai$M>{Kdb4!Kz0C3Rw>KZ{|{DCC021c77JE9r)e=R=)b%4F@2Z z$ms()uf;08nAEUp)AAJN`uBT_=Cu&jeCGO=r=4KQfGw2&r|Rj~y+Ls2Zb>9$s4U8D zHai#@F%jr!tUlUBC1*Ohvf2S`xY>*IDdy_Nv#{u~!Nd-&ulCXVpSc0AiJNp7?N@|-$z21qfv8pTJ8jFYYB@HvS2@h+wx|4SIuTvemywPh{RmRUV z^MZ1p%2y!R2aR3_)#o+%%McMl>J4`Yxq}UQ_1e(v6(?=(hE_=q)bLC8VC~J90-ud< z$&;K^GQY4{;)s{Zn)Mshc644wH#Fb!OU=AvtMZChir|{7!aR9cP+NS$)7@5*(jt7D zaEG|-_Z>dY-(t=f)FBQ(`0!}B#vPTsh^JYIVD)~WGalC0wiQ1c9Zt2{t5NxE77$vX z=)h|788cPL{axa4wzalqi2-Clf)$oT|Ex8KDog$$yw2Z~TZUYI*v9~- zgl-v+@8^K95d*>YqFnwfAMN={qXYt4&O36qy+2+@ztLJYFXxsKdDR!gM~gik-=Nu` zD0omx@B!PPtW5-1cR$N)NGr~r{oQh>$Pyt*tOBwO3k+<#&Ipsr0~RI6NQVTsR7qZLE)OS?nc*6DIL87 zH>oL_&(p6}2o{aA_Zk2g}O(MNBw#wH80?tTYVf&MGhvdvD3`NP8*0 zH*6XICVW0p-BL=>%z|inwm<+qrXJ<=c5Kkz9b9(tRYM3auVePooVG{m+hD17#9l+x z+u$jftA3UtZ5o&mEV}tQCawW>PTT;TYBn4hk5Mr5OI=1KVyld+pOX>GDiF}Z+J=9VDV1lK!I2TZl2ovEV-IoJjA)jkks&cvaY&NgL8YOxndxkB*@+(^wE~c zrOqLM6n3ZvCipArvE+1uI!?*M1rboLdU)b$gr{I-ACPD<(eOd#F95%mTa$GML%hX+ z2~n2E(+w9n;MEbQP3Wp4og%EZma6ctN-ghiF+4~P?YF|8a5KbPb$yHL#3GQRSL3}O zf=p##X%ufe7+{tqpdpH>EQzFTnvCE+s0Onn+24iW5*8dQd1eqGr9>8d4(R3I!S2lZ z6krD-Rae7iZHr)MNo$G>RZ`d*p{dJwp_xNZ^s1Ih1;^T7yK7=;dso$|NNsLCq_SR?ts*Hywr#}#&G6#g!eN_| z$cw6nW|BJ?Tl@BL(Rq-!adk1} zOO1CQlj1rPrsQ5dtz|Z7-Q}up4-A%~t4b08>~!JG3VSUFeb{2G`lqIWkePa$jiqi* zu!!qwdd@2RqB&zsfJB7_wFo`t0F~ugjpVLwbiXBxCX6KMpc}DPjiWt9Q}U;a0+d&~ zDIvr~9B%lKGIxtoYq6+IvZqZM(OTA;TZ}(H+w2Q%g?S^xGRl*4*{7_iK^y=6`g^8$ z`KHaY2|a}To>C*P?tX296Q{5-53cDLaHor$I|Qi*td@{#%+&|jxj5%&!g9657aXK= z`Ta_ZsHHo_N^aU;2m4eRGr!!?;rc^xcbk^3V8bUqt}Kr+^qD2Ic4kb%#o_G|W2?G? z_9j%VuJ8CNeQZR0V@(G0UCd_K_RALX#j;GsZE9Fva^UUlZ{Er~T6ezBPo?hNK?D_J zw7Q0kTu#v5+zHeQ8uT^#sj8CuHtn{V*kQK=mPc>-J6m;$>1M4 zDp-9!QU&73-nH4Tdt-V!t06oJU9H3znh2N>mhqVOox)w*ayVo~E-SYA$*t15_3j9$ zrQShnGgtC!Tf}m~yNNCL9ijKo3gkB@ZgVW%(dGfDDbp{TFj^;72kSSrG(tD`d!#z& zvv*Fp-WgX-39>wqA-brJ2P&E<8_;rplc2#AY?mW1)`taBIk0{T# z*OA@chPuPwzy-z~_MbeYZKe5k8@nuqSIfFfXUwarC*$fB_|yV)FaK20TZv18I~G4Y`-M()3P2mkP2LU}hnM zu4!+kwp{(SmV4z~_qDPSD_P6ky}{-}SHB6xNIx%{LYB8923=;#7N9B?$RltG{{H4H zyK}vc1h1E~9N38_M6ktP1|beqFA)v}y2Yu`^N%|El5T|I668WicjO9i+|nva#FEk? zxtBhz@40o{j}E!Hn2Fb<2bkBshJ#LBnaG!$- ztC1n7=D>LUt>X+>{ONk2;SSoremi6T{d{0v90j&Hvz#<&gmH=#;ge+{$hMcT-1z4J zdw}1^ch?BS_B){&IeK)mW`q^z!DoT5jDeL-Ffnp5(I3F@G>WD-FLc26CLtUiM{?2L z#CF%GDIm|AB$_Az=izlY*xchsZ!kN%2_pzp#=gQ&NwY65Wi@N92MKE$pslJCp#CKq zLf8y}QcGAUe=OL%$R=CR2n1-2W~gcFp7zPDF2H0J6y#f|%w=ODtHo>j-KHiG3AIs# z3?c7@)+I^dGA!f$r-;lGgs}?UpxaUr$mzq`Gn8UO+go`Xd0mnpbe!!2Lf?ZSzb>53 zG@OMk$&vVQ!C5UbUo9DgH{a+&rXQ!kcVpB&=H>8wu9LOFp@;4ZwMZV6*$ww3(k0=73VipJ%@xM`+n2)CUJ0~VKN|iaFqh+P zq%(3<1E-VrbhzpvW4drny_6T%%$2z)Q!-r^&p$W&P8}?IGV-e(H#&aagwa@T_>dbd z{i|(_d!&%5)N~Q{Rr0K9V}Cehmp)dne7{!<7jZX%OdHd>cUN1>=-UEu>kBxV!`Wi! z_z9y$b8p{@sF2zVdd;%~DNQGC-7WsZofK_qXqD8+t$H)Ab`H)}92Pe+5#4l_bGhp! zwr+H=E=MM&){ou?T`g&?avDeg&Ua_Mx8g~BUdJu9`XwZ#@DwDE{Nht4nB;;?LOguf z@Rch=1`v;GR>Nzpgc}gg$@)nsIE^_WBaSGnGwM971f*y zo>XF&%wj{&*Xm6`vTOYP>r#)3&q_4Y3CR0(T~pn!`%w`SXM3BACr$H@3^uHGFE>}g zyB~t6;=}Z=i-fuBxln37Qi03i4W>f7HKc0MP zACJ|}DZ&0C^+K!m;2p12j;pm|9;h_|(EA_mlyF1_o8OR4Q#9rihgSi&SD`M55o0MX2p*4m!L^`VXPY_<<)`ZC;B37a-?0dU%KCzo4@i_@Y zK`a^0FY%P5WOM7)QhO}Es^<`*Lug~+2xAQOcC4W_xesyi?K;;wKTVSD3-+s5TjJ6#SF=um z;Y^GTCsYB;N9uLw)1&RV1Pd3>M|+uXP~?mEL6|+7#EoRa2W^86`H~~AF_e#jy+g}# z844&ZcF9h@?L3UqbeMdn?bk40-R~d5mqNZDv3Ww-3)H^FlF{8CW@$egpd@9$D z+q{uqAWe%d(TX0Q7e9l02^Vypm<-)0^m@r2Ph7g?d?7>eLe^Z}3O#(VwZM+aNA6!v z`EoL3dgLLsv-28kCJldFVcGmh?vr0)k3JA+S>Zn=JHcYYNu zE}!h!<}qwp*I?eH?Xz~RiZ*_AE)ku$?KVkuO%k+bAGzmMB;Dp(;AxEEb(AZ%GOLB% z5F373ux;0sZ=RI~T@tXjwK`^8C%y|GKDQO+roU2qo)OCjYxomYwqL)IaJJvKD<$fA zbGb&SZRWc;x>YQ3q4O*k{#?+Rz=-vFIU4ouz~g$*%Z9 zEn~kA<|lILq3{SK8@{FjpH^oUL?-65BHRD$0y?nqe>df`8nUdKSTT<+sFJk}h!tDe z{~0Wkm7Jfz#!_{Ie@9O)4T#X;qR5dC^Nt`j!44bKfTvR>=@VWn9bfIw;;))b1U}FR z5)p&Gg|JgFl|F2?#5~lghM&a+7vdj!rQUiW>BJq5a&#@f|3>L<)|tMqPK)Q0f6jPv z-M>-mTG?+q6opd>%~Fah1Y_pt{EwEtxgfl$BSO;N4|f||UhU)^o<7y23K~(f+QQaT zFd57jA#sxPSA7e@w{gX9#{4{cDvDhD+}m*o0o%^Ac#Mmgd6>Z3{9^Cz?=CGY5iP-gi&?HAw2W~9p~f7qez ze=Gawnc>fFY)aONEzpYq>Iu5AK4 zzG&5H4OfT1jKC}eBxLZYSi(JP{{mQj2BMk;o^Y<|^-&Bxc8umR_pv5<6AsGh6}+|H zM|3~r+zR5vMlWVb4soccj4$$!8gFCdGW>77HbMr)DjroIH(%q#dJJiph=cCHO!bD- zFEnU;%$Cv&7B{DiMpQoQ8*u;hNe|9BDwI^4Y;P?o!~-wLh?VOVv7oHUHEC$*khV=* z8x_t9dJ}N+!?q8qPzA8gn9$B;PaOlMh?2w(!sItya$9QxZI`~A$a+quX@ zoG(9w%AUaHz$z@8A`gSh?N0@<+plM$v?laT;Btf3m3i_x@Vk^h!@i(ohQx*c0$k2k zMLoJI8fxR;-Gx*y{0r~`^A_8&u<%jMxenu;pz`X_+rCx$WpRJMs`u`bxz~;mo~DW_ z3P0_utNu>z*l0OAZ1iA|p-3{3*3FvlA-V%g(;xF5l42~^pRG>iuMQ8ZWp-?2*|b_! zhQ21%-H9~}fS>}YnCnrbpi9ziQ>_~bL%1zzrjiAlj@COvY`{cIsP`}{k_aq-f!l|iITBpEeHCxY=`z&GhQGrFwntTnEf5Rf--nCfvmiM8f zm%An}&2I)L7OfD@sOLNn`HpU0d3DcL`UGgmj?L{u8bmd!zjz}IxDR)DE>OAJMYwFY zmDaD-?})o!ykwW?Xce)1N6mMXehX$bMAAM2av5EI!ogEDK$tiV8%Ph#vr>zS)pZGt zEQ94)Xx{TBmhLoeUg%e8=*_{sm3QK?iO7myy=^G}M82rNZceO#?^98lxMhS(+YXd* z^Kj(+y-IWZTa&>{ET?V z=@lb+ned=8Sl5t^&q|)?_*J;JP1;iC%Xx}wfhEsPZiNkd?N#O()ku`+$5KX3@O{i|kRE3!01i`iM9orKx`LiX5GUk86Ta>%5SAlz z1emD@BNtsC#pfUN=c`!rLn>$gxo7_~AtxMzwG}!!wXyn)SZ!9Npx&PRt6m`_q1Tpfe;n(+8m=(ytZYh5oph|ycXnp_KH2) zs_~_@JO)ttiWXy)88bfp(%=jIMt2yf9Q~=!Z(gR`K6E{8qQVVO^0^Ch$@4_~tqiem zWpf{g6qv$lrJPDE(!r?GKHYG81oU+%#EG zIAKd~Y$K49$*WR8jzcoj3!L+0nz3jO^4)-|R~CvK?G;vXE#cU|d8%3qmgAHhmFFg< zk)sif^-oW1E;^dMwPcqMfsWN6R4_v)E7q8hIAEY)RF`U^Me@4)REN!7xEZOi?nNX) zcoFB`sz%7No$JMEl##j4D=uU)%6D^4z*}fznLb*o_U#eK=~m&oO`#Pzt0pk((w%_h z_3a&XD;AMg5~o96X>kihBO?qDYuYDc5hz!KTn~!$k0xKWM!zJSpJP~7huTF#T@E`G zpi{`l6tSUpz^-#)6cTt9F@=mwaCjN5TMaD8K`UF6X$u~ebme(iT>k*$rjV_3fTD^i z8HFV+B{2mQQ9uPDqKaY)D58M{6j4A6MI{u*biNR=wZ6EzTc}o0sH6t%&1fft(0G8k zP;~G-zzOPqyZ(DsKLt+=7dK{UQZP|*y@1a(1U@6UXf5>iZUw^bmv>nAbpHT7spv~? z=C$XCEpB{46cI)twn#k50&$<1jDMd>jV`=3E$8-du>cnG)@*e>I_E!)T)Wq8Ec{0i zX;Gk-+#@<1g*%BE#Z-^O`cyZosDEk2_Emo|QzO?PZqM|ltQTd3@a>p}^25qQI9>3{ z-+K+5XXrs6m1cN*&$h|M3EB~n*#$o=MeR@L<8;zTyO?Y*jjg1AS?_2ko53z)W= zRgS5tO>p*dS=!qm4+{;Xw_be)F;PpXcw*uyqBboS&~`c|3w<&DYVEE4wT`EAXMLzg za+hcv+yE6p!BP$m2LM$md^e?9MJ(4EurZZBUfgH-{NIH=0_HsMLb^n9ssQAVo4>sqFhC(xtG069+>Q(d{klMv< zJhDI-B#bfbnq-!?*EVZ$3YfCM5PdU>nQo(o*5$)S#7M*`J$bD8wFxga#N}d(BRfS_ zgx%cJmfG6XD*cJ2LOF0UMPkk6ncvD%sh$C?$N`O&lKox*)IJIP>V?scYxcN|0}O1t z&#yF^2(qu)rV;suDK>iT;*~Atbl#CWFQ{6*bq(7*(+P6{1cD!ezL=?AJBj6jOIa}h zQPHp$9danJ-0Wr~ZpzFNimy|Sm0}xNWZ1$sA&vpZQ(AFN8XMVBLqyUjBh+*?+IUAs zT}BA5+B`uN`(j^E2U?|0O!>?N9M&^#}tYn+f5>;BA7)WBE=&PDdbWSOhkC5IH_?_xTb{2@mg9q zRk&#zu&nU4rKiMpmh5XtZf8AZahX6GDLXvtlXXCw|Y%qL0s|s z%XWg{kc<#|FPV(yNIPq$P-ky;vpfudi3 z*Sw$l397cFU{#$#hxRS-{{W|XKlBq&Uqi^KqKXVeqKYYqC^S+~Afk#W0HTU1h$y0p z04So0UE$oH|o#K}-iP=~ANR zn;PWR*dQ`ERT(7U3Ixp!2S=M$zDs!2;GdZK3imw=ONPP-;Egb-KRUqhhJzlFr$+Jt z_K8MV=b*05oLk%OKr`!_+A7F`$fSUS z-l%5eZpPF+q$1Tff#$hMBWy;$tyHi>l=5YiCn0?V) zZTg)-WSEn?JP$!zT9w+#YCw=TKDCs%CuT=J^*~+l`NXFNlM3G~wFEviv%@N}u)VJvOcV*55Ytx!?`Egp8 zE;@SE0R1x5%Xse1kOvi~Bq^||Bdugu&Ou)-z$LwE`mV=!mQdS|9MA=jh{&i11Nf2vVm*jGYE$L;cvlA;^`>tGbswD~hIgc4nsZAE1G-qj^ezQJF5nHH zJ`?`{Z3RmjCl-=OK2gP0xUytZn!MV?A~2~WAo}@&t50zouIIUj9)dwo%SKG2V&Mi! z>sb1XEN&7|IOuC#=ZC18FZ=9OOP7jR$n#?V0KUa@PgHb6b3Y=To+=SEKw!w3AMFYZ z@&5qNSpNX;p;$Aa6UKjdD)Bs4Tyw|f58g-r0DTI+W{8jVvHt+yVx(h_G}fjW;ve$b zKl|)b8RGu{p0WP`-$H?oq{SebFZt^q{q!k(=)dQzfA`p+LWQWL12rL*EPt(!{`(aZ zOBR3D$NvC*ifBv~b6fTh0EiE!O=4RZ<+wx=$I1Tqu9`QqL9ovjanJ7sYXcgLivH?3 zViCr;BfVqJep=VNHbNFFJXr(#;bo-%*m6+#wQF@`-$bQKcY%27#_=cyFa zaT2gTbYza7IH=;eMs|0aKD{@J0I@5oMiq@zoUHGhr7}xLfC&ljR%E<}eZE}rV1KqMh2Ep)sf@f}4&jAJ%jLIi zV0%+p8bP&>8LF07aXJXvSn@yG6?D97gXcy60DX!EQ@sp?E`C~u-e!r2QJmD;gwa00 zC67Pvie;&ZCd*9`!2bB43wH51EbQMicBvv$7T|JoN%p8qXU>cM`xG0vbHvgA0N-M$ zL{EJp6CT`y#XwHx=BAbH*lzP;&->!538E~2dmsJwDG0^!Wr|5=mq2{W#dEetXcg6X zigax#sF4pp-WADPxF5v7opIJ)@zG1+Ity7U)$?aYz5f8Oc|Y_MS(ei$A3A^ELapA+ wpV=3HIuc-i;vd~Lfwx1%D4+t0D5fByiYNpB*@E=7ng9R* literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00004.jpg b/sample_videos/output/v32_01/00004.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7607dc15eebbb85e4a65d95366160f126ae51105 GIT binary patch literal 21026 zcmbTdcUV)~+C3UNA|PO*RHd7QA}vTaltAbtK!5;BhlC;>!3HRZ5fDNTMFgwqn{1L80hYkrJ7M2tdkpA zhzoMH@BnzY6an1gTs-1je?J18Uk4XA4=*3TfZ!pa!<-8`#Q@w~JUraI zJbZk-yqv4Aa6Sj{it|Y*Xq)pNMfnRT#sGEF@@oW@ELw*^=($hIx&g82hlGwDmz0uL zQ9T8QK=okyXABIDEUggMHb`4LS2uTzho=`dFeo@AGz^E2i%&>Qx|p1D{YFOS&8%Al zVnJb1aY<Q2UGhsv;Th+OaH%`*?%VXKl54! z2=j1pZXS;~;4I*i5tw`(P9{Kv!DIs9zlNph$)RJ7#|R;Qx=ICmn4%A?vCWf3p%<|c zKU2ic-pF@-K4~v~vzPXC^8Vrbsn7zhZA_GmhWoARAPKJAB8>SCW`lMQ4jPxL2TM^* zaUKlh(Q1%UT5b`y+dFnz%~Q#3N2n=eAv50sUQTHqO8)jM+9~_oNi!TfAjQOUzdTERsJiR8gkxAZ6+PirY>$i9( zv^o@%{`?F5cK(jTMFkgg1oTQ%$zM+u&A%?@H>Hi3h4ENi(P#T-W(b-s7A3z550xPs zi#U;nOAc3c2I1{~Vv|4x2JfEpzg>!NrThi zowv|t`ct(I+8IC;+!I_23`lK3Swq6) zZcxbt?jd_yd-#~O&RXY=A}?X`M)12t=2d_6=mlKY;QA(LZUkR$?RM3@pe_}_c(WQw zFC$AXy%BhRvMA)a)pY~Z9)?a783;f2#lL_BnBGfFzj(69Bk{bsy>&7^QbMYB2{ zI9hat50eZj|LTBXtUpG7l)2>Yw?J)yWaTEw8PMr$RJ?@k zz~nM{9K=zDTL?@pO=Y`;RhuJ#u3QT!gkm}YT+RJIgQ7(yfO#H#$|mUPP`)U`Vz<)* z37Z|haBt6!YdazL{sx47{D__-r${*ep!HId7iY0JdA8j6izZXgIQObl`KXSjrOb`T z12`@tw{bjh8%W{c9mgL(0Od3bhxyow4(xrrJy&MY3W`?AHbyW zPR^nh?x5J*`VB1P!U!gn^TkWksX02htc)yxhySbJM9PWnPW2aDOmSS!Hjk-#hb^uoE6__cs9K+EXO(+h~|du6L%jl!@CHpA85r z$aF2OTZ&@e6+9^GDn6$^s<-h8;hNm?M?B1OhbTFGlue2{L>`JWuP9iemuQH-Oa>Kq zLu2w;-nsE#o`y2GVqJ~Z34`AlGT}svu1iZ+QIc!)vD1aAqP=yxw9JjP?4@jxEm6nV z?>K`F7^@kjK1=Ax!r_NEay}_<5(SswU5gf4Htd>snd^@wp3GG zcJsrV7cAg-gg*)H)CpZc4pRjPLMP3)9R)~eoUmJCagALm8giuWD9ecS zJWO)gl6~EL8!pB{V%x6Q8*$&aMnyDTq#&t`Ly}m@!s?*8rS8jTYt3q+CGR}HhvoJC zw3YruLj7yavnsBS=k9RZ-~<7pg;R&YJFk3R0t|pt`-Sf7Af-;ia>fg>e4omthLstC zY3OKu%6bxu8T;e-HN%Ln4w+L=j#pjyZO;*YDku4?nyP(q;6#JNjrkUQIIF>0z=t$R zN-An|27!R@xkxS>@p{W((8LYOk9z(ev~TE^W0zNx>JL{W(F#$1Fu(g;X)Mouc(Tz; zjJ$GuV{KtoU8(0bLwWG!RyREtuKYxy{)IZ4ui!(~?qkb~^)fPwX>|$Wt`JAAQz%oHQ2O;n;A^}jSlxlw@4kw~bgkQx!>52$wUE5!5jCu|+ zWiBkI{cJK^?X=^cTx^(u+DR!e-SR^2_3*lh90#V>0T*gwp(r_3ZFp7}8`O9RSUA|c z4kTP`Q~@2a3y@Jk_x%k({|&IjP+(4*599v^*ir0t8pwopZB7&p5Sue6r||(%4*8r{ zejI!Oj0oUH@P7s9gJ8{n@PPpe3Bdu=i-`n&4NXTCV5SKYfovCdK_N2F+oxfU`R8^P z6gYWFCQ<$LkHPWq#ljNFCP?&=6nGc}q_Zfw$@0!=M^Yv-l!RPxbR1A?-qB#*5n#3G z`*vh8sgC!^QY}m=p<#a^O(qVNk0wPeQ*^wILwYNfP{c9N+vQ`qUB!*k-=$71JUR7B zLoIpV{@%6X0qAoKIGHkT&H!f3kq=sjx#tYQFVyJ?f{kwXSD-R_5=scx+*ycRD~$cYO059mD?kOOJ{64 zaob<_V);%H#&W*zHbu86`#n|~Xe*Q)0yW9}Q8d1NhEkM8daGzS_#O=IB8m?+`u5PO zLV^Hpesg-R7;H#ui%R^VR155(=w$*?_V7bU z+(Krm%r6w&mfCBw;~Atc>@U_{n1Pr+VwJ-^g&rfFjNJ6K{~&iP0H9LH23I*;cC}Eu zK7i(|0?^I^VQu*&rdaMtENzKfP=q0kmfWPB8z#2pK*%ToMGF%G5%~_ny%%X7XWy>8 z2}fVd+VSdE%UUtZY$y(%Lx4g8Pzc~71@d(Sw~z=>I_TEHe}_XVVjQNSJpLCX4mb$# zufYREZ{@|4^DX8$;hX~^IFn4wW^;ud0H!AEqb-7`*Tl80svpDXxSvWldfZk}O?fPX zj0NK(Y@iSp8mpw2S%K>i&Y%f2i9!Xcg+4CTGVPw@GV{bqq{-FQ=gNV-4R^5HCZ~h9 z>_YtKsfxtjm9WGnS)powH`iSj-uN)a-SBb@W{ufdHn@?icH^^N_74S~psN1IgyO8% zou(Gy+s`U^V!N2nTCm1eH$5~@H2}>CqQ4> z_wBtmE}Eb7d{Vj6JKkK5Ow{3eIxoIxDIZw1t4@^b6pp#!0A=cnIzm+DFUFPH{U3t zr+xgR&|H^+V*A47;SN7pA?FxfYFo@E;lVvm3{i{^N1?1ybB^>z8PJ~{VjR*LZkjj_ z%nWHYY#3)@!#?3mS{=4#uMd8{*2W@(cL%aWZS;@We0XSMt>Je{?S^!d(UC6HpmGepVU~O zmwsv?NKI055cGPPJ|5-UJ`P*GNPNi*L4LcZ(N|fP>#WSq+VBidwZR5F@H}Zpq1!fr z^_$n-Lgr#mxok)ilZmjwxA>y2!^iVp*}#P7liOZ*`o%&EkNUmL zg?{xx$oj-?rGc82H3w7Nd?Cqkq-({f+Vh7Sx{}EzM3}T4Mn0|I1H%NPV z3|4cZQg2Mf2|Bg?!Y(K`tVcN%e#10zS)?K77zQ+p)4vwhFJuFHwn82`F}F-QeAL$` zQu179<-oHC_$n7_nrahE9>vr?WcHi-=1uONaF5}r+OF5i=zw3m zh6GS4zzzUfIzjI-59H8RaULNoEdFu}!#H6^%}o3&hDo`URqgOF{7{4+rb9m)&XT_J z5U|$CRu{@dTgQ3tymP0*`NF5~Y6SW8NL33-q>5NU*k+D4J+9(lNnrNHx(h$*I?_|+ zNU>(n#sboLT|$5g>0D=i%A-+E2=q`%sfmq~`BMa~Qs!3JQ553OVL0Vq;+a@X{1pbx zIhf=5|9~hFoLvdE=?VslZl*wxOqGA~f)c$#7w$P*4l#CcPUZ_=rPVzKOP`4-r{Yc^ zf;_vqJBRiVmYPZ;3iZ;scS!On+AQWg)gy%;z9@%Wn7+j3v9y6l{Wzh0uBbNcd&C{P zuLB@mh2Ni0Jh?!`+zm|Jnflh5pKVuKKq3=6=aSC6Xnw+U?z1peBjibZdkKkBwn{&rrZ zOe~PkuS%=R8?1)K`Ma(<5^-;qHyyHl=3)BBwW??)9rpRTFgoFVodL1_& z^WX%jsP6q;S#OuTPKs9-Mfg}1uOLb=|5*op+aq@?kTB4{E zuz;;M-6@kKp8|NfQqmUrv_oP;8SREcP~At>8Vz{FW5tMy`F%G#+#X9yDQ}mS#Ph@X z8!VFPUJ)Q=mPt}6Qzd{I!ozuN_OApW3j)@2Jz!8lPH6Vy$hHPOA@DCT$BD)0U+gJg ztA~QKOidt8SMblr;r$rL{w)Op(VqP6k)`UH3Ritndw5ioDj=zua9Y%)HS_`=ac&Gk zuv>b-4(SXmGN3P6faS*DP?;+7LUvd?02acTKb1nQ4r8}n<%<}7X7`$LQiQPD9g&8f zv>GjUQq1X8$!no~#8kM4%0AY)?~1nuu7>2XK8ikKsCq9ANbqrsr!DvIn-8a6`oK)# zJ)I>`rzZ6w-u3DFW&aZ@s{*_pGO||#GfY`2Hd&E~#ah41Bh=(S#99V~F3QgxuU=kw z7auNJn{oAAc#1*#H&A6=^V8za5~`Bhty&Q7B5h8(MTDkMvh@^)mK-%Y`lov-01`tm2s`HY^-XXP*NJpI{`6W$;;FsH6*tbc-i}r7RSgg8Q*@nerYmJs+G>=EzB&2sb zybiN-molgyJ3RRmMT#?5NACp5&-W_k=R$Iv1FlI2BU90A(T)y5ivwdq>sZ6|P0fG>5CwS<&hTw&D36s;%tBaD=p z@<5qEvvZ4Ee}P(YDqoQ=J({*}b%~n`k1fVaZe{H4*tl7ziErjyx$k<<2cTivF%f~JJ?yJ5q=kB<9Z>DkM@*!*_0JNl`>rkMN9KF@jOwXzVRmfTzC4n}4?iiga@D$VLcZc3dWZnURi z&sY-FHG|^$cf4yFR3hJbK7L-?BK{y8udKG88XcwR;0r;nE;)>!SPbEAP^iRi$kLa z*^L&5MN{PvK(}N6PIXj((vVaR(cU(AcGz?QGk_`2TUw`-!f zbTxxf!%@eCPp69k^c`uK=kaYU6K)8cI3=K1U1@GfiuM!0-b;w4)vKn-+7K8;wSj~$ z`$a2iM=Z~kta)!3mnilx?Tk1*N?`|E(GS+UVg?IX*a2|i@H#;a!~6*<@`&)>VydFp z!?9#KSH63ssEhj`REn(DLn8`3c;_BuhX9tkYP~$%%#o7t(K?+ z`I)4t)~lU%?;R=UeRU@F$Bk*jHbUQ=*=| zf+GtbNLt9>x!(CyK4*?N*hdYT5Z-OIle`C6QcnjUn%}@5?xy5r+rV&?V1w?~Nsa1D z-*V6BD6Q`@!e>Qoq&ID!W0!u1Vs)mZrZjx^jqPGzt7h)=`Uf{oZ})kRoiSYNyMHN` zrt7-0OuxKNYIEaqc}C75T##gjPWJ?2V})v?`$O_{b=FR5r?_eeC(bwcV+I+s)2;ibkR^*wv=Kv1ECnw487bbQwJ6= z(pe+Bb}?2pT>au45N{}J^b1_!X23+IBSgDSL`$)^99FIrG{2Qs;Qp1R<9)2`^zWQ3 z&1XLNY~Qt-IwgGqxJjNPfy)$Z?QIXp{vUG@g;3!>$yoy=j(?9lco%@QPCg|9jruaiAckmU!+&+Z3wNn zuho#On%A1fI}_N|90^9ALg_4aVQ&?uLb@kMmiu<3UHo0E7ANez%}(~b_BXkXg4Hn2 zG(OWU$(GRKbFFd_OWSt+y1DU5%_vFgJKd>G@DA#uD*z4?_S777f8;}`)`EEyaU?Ws zM#lSgndti`)wX`8UM^bCWveSY;pY`m5wD7uJx}@E>5Dc0=zAfP&JN$M*Se9K3tVgs zAljDX*#3^`7gU5dR3GtoXVqxehsIJjb6PJDO-{N~Itgxjg6dVfwASJcLR`*mz=~ zLj-+!41OD?+#bN)3MyL@{DEr^Z4c@6+Y@Vj=Y{>FJidDtQ!h7ucX&qprrxS6sT^C8 zj-Df6Ee<3Ce^p1}dlZ4!IpV56b}p8qf`2rfTgii@T?Mkdkpnd2gmGC+y3LrKrQ(-Q zMs5-f-1qukx3pdhBFx?~FC>R?$X8 zr>NW? zZ3E}kfzxnoRdcfTyAKK%E!366vKvFK`!x>YAN-SBMK9NsNz!t3Y6;nI9n1& z)cgl^|8lsB({9^UXw|WEI|w$8)H8>Df!E?aPE*g?ZND;vhwJ+wf@vQ*!xo$%$6ON{ zK*Sg7HrJfY*v z;b;jn!I}5&<`?vG^4TrMSv%(Y_SaWqJZhq%@rMerEFSUnF@EX=Vt}!9pmnw<%+FeI z!`(o-L(!1C%#x21^ctk&P5$19P{pN=aa{!QG@9baeZJt@PRqJVRf2^eEoRcU3vwZm zffLn-HdLNaL&hC0R4sP-?-^kdR^gA0MLP?UQs%P1vivQu$GL1aaZsulECEl&Nc`j|Lwv+k?j4juODS?kfjzV~_2e)_qrP(v;B zo;?r;CO2#e`M5w92BRQ?_46@sVQTdp((18rXT-8RIKPmd!Lb+>}ONR90~hK;Hz^-As%qqBxejL`eX@WhaJ zEKP5R&K_5 z^<8mXcYioK= z1{s1u^wOEpW&C?*c6tut@;uYkrm@&AP~BBBB+m?IyOvg#WT_KSF1)7Cw7I0F5Kyby zd+RQS@Nq_5SM}B(QA1fv{Pj%;+}Fv~(CYfJ%UF3IV1OZi+lNTL-H9WURfP&| z%m)LZ(H1v{OmlF{&-wBIu_7+rpZ=_Dy9pV1`^PaAA43@%f6EpxQouQ|PDqV1mrKVD zw3L5U6Y|@ZeH$9c*3lVam?7c#Dddmvlp zmzTYosmr&Pv=?dlg=N!kN&BnUfpFRny6Kq7;tt+G>0wNiV0IN3{qquS%sBFciEAVI zF5wWDQ?(G5lM8X~s8(*+nBE#h01Nl(ewA^-Q;QcP>?8fO{j#`&_~MHF7eXb4%Ndp< zylKNvA#UuvL)M&b<`-o7JD*l%tWH&Iwbfm9L@VS?oPznT5ImyrhN65Z6~l532Up7q zQl#RoLll;-UdJO-Ye&D7Y=;{Sk=pTK(-*h3~< zcVVh%00D*n_USofvjwqyi6zsKLz?9B-DJt4xa z>x?a4=42VsDbiWHlsd0%44+EcDJM&zio2Q#d9r?6y?2E1O(!C@0>54vc@tinpebk? zUS&7EbECIt$^))GrFi9u6z~m&b~Uj4nnuZ=UgKs9t+p0=FrpLGJ*8HbpxBM0>Ol>WHW7nX% zNjM0#T>2i+PH6Aj-wOiSku&UYV4rQf|0bdwIT~ z2)HE)yahGdSi~I%MUKI+_#mC^G5k@l!_5YWD0;m}bvGqcV_ zX+J!rKcT*(4CHR)`US@Y++l`JE!`XM1)&jjHzutvFUn3ay#(zSlGM`NY>P)ok%kXG zQ>tq^(D_JQmJ7~y@-9`^gk3D-sN)6VbTa60bL`C0bYgMdyz&7PaWJCwi-&AEicUJ? z;LiUSLbwkA%_;pY|HEB^99aDdo(ClLYoOWa$6R(0Fi<#}QK|1#bQ^0F_bRn9SnkS0 zu!sY~qs=heod|Ul8;)@q|0ALCMB~g)U}|IBDePF^)y|ZpnQ+aQn+?*d=>$70dBU%# zrpVVx$_bIeU(o1{&p-XqDoJw4@2`zWrT+*S_k)%LG^|z0@ z+|>=#JaU0KvhNhnfVn;;|3`DkJ0C=T9#PxPnjzbMuT*Ag)}Ve88|nh{`k*2{sN-xG zK5_Yj_!pfRx5(Z5Nb31^uPFt&Xe0M+nEezL)7k2OeYT8wOdoYfZ^G^)&dK!M-GMT{ z9__MN<9uJ`-WI7H_9(+q$Q$U3(TGYuJ3eiGybIL!D6pq~hqB^e z8G}Te1%j#tTM;INCIaF`TN#Z#X$PWedJCiaIkN=$q4%g#e>AN*;BOAR57X0xRMk^BU^Agy1T z>)-*2md1+cSp8dmali+-0z|olaS-^cXsAFgCZ=-o>(T=N3L105sf#>?N=`cnrQ9fc zTlj&E#C|+1H!hzgvH{ z(w%+!>_yZa>;NlH)bplgOo3s)0vfPN-1uyUDg4pKzGFvO77Wg%5gDEf3N(M?mn;K&itCYaN1XP1F6ZN410R zkY)xX`MW~Sbrz*O)Ih6L{L%E4l#$_G&P8jI4k9brI^iR=Z`s=6zzPSgRXUy=Tv$4o zo0G5qYBV_LDG1=)-PD5=Q~E!lh;!vH^X1^aa{!|73keqgh6V4xPzCtKM;yRt<0hlc z>(~T@5{137-~Ys_9;`-WZ*2QIh?FwzAwqwunY|tedkC}Mh%vX;TWhbEU8?v)dbR@1 z*XzLvlqJApDxBgwC-5Omk0$W+UUR&!Wwvw{(ptIh_IFWT=XdzjxXiHBh84 zC-0N%ts94c1T_uvJ#+M)9hs^(8U>c(5lVMWb2Y{!Ouq~z@jeq~r@d%k8K9QPRj<{P zX*za65kgH;U8bv(-*S5_1h7Q#Ctb^3;CQHU5>5x{mEV#Q<1UoHG$4IE{ZfLLB4%(* zvHe27E@-%;Yb-or1=H-W5=dYoE2P`w{9#Q+MM7)U?n5l~FGm;` zLPG@z$Rt2?;SyFlyZ6Fw5~jPQQeL#*F`(%AW||QC@+#2FLxT+9jevA|Xvalbd#)Nb ztVK=pH6hGls(c{mMU@C{^(@Nkc&H)GN>}3LOHzX~ENZ=&mJPj;$wtpDr%E5VSiA=W zK|81eD^znZKse|uIPh=(v6FxQukHdkkmERW6MX(vzSu#?HPi{)adggs>j2=KczhHJ zIAtF&K}xp*KZUgmU+a@ir0;J&Dio|oXo8vY2hhm?x`4TGI8L+5`FYCnOAqgm{aO%n zS^pU#ffQfn0s(gGWvw;(JYo%W$)AtIOR#r+(q5Z`+a$ugrCl=iI=#$@R z5#>(cRu3tJaU@qVc@XBI+IRNI8%7ei*!@rT-I>O)-`z@n$OnpF|0?|rtYGhX%wPe) z4_!a}b`o1)BdguuTLXU&t!oMgF0nSd4Yr-D93T_)?<5SLYsEYfcH6KH7fE>=c{myoCcW>(Y>2iU-LK2 zHe8qyaToq_BE=P1qKGM?7=TQ)Go(!M1`DV0l{li~3L#vW;I4N@G2JU<*bpP}~A!Z_E{AdSu5?tY?<_I0L~(6|7BlhuU*mh+CZzHya(yV0YUvE9*W!a$WcC!9^pn>-m-bf1LUdWgn9?K-@OM`VjG zaGe7i?$O45TvM6AOhW{cTuQD(oT73AbkTGx0!i(1EPmL=!8*P;rnG-f;VajEst2k@ zq>J|Q^3$yvwt7^seXsnNt{Z#ZyJMY>1`;7MA8Sr_EJ_38QrS-B__&PIAdM0bBExA>A zGrp>6 zBhMQo8nh1-&Q<-n_sR= zJ!`6)pgy+M(C)@F3zHG@d7NCQkV!0Lqg6O6{a2{`r}Xw;-Yl5w8I?lLukJu04%qJB zFg;LIDgcdx2aeb}@F!9a5U|yyP!8@AB|y25WIxN1&{Hf4R~$I`zz2e?=+_cjn|}QnOT*<8<)%E#%Z6b_+lOL zk;iLlW!y7hb~^ag%Y+G%qHk|qhnFlNANLXsu2IRm3a8Bmy!@mViLTtdV+W68;MW`K z{;V8Os|hRoUblC68!kDw+-nN`reDL4Z1U&+UQGy4P}@e2Nd>0rI6`Ye20^XVrIH(&3;{ptiYB6lhj@=+-n5lfFLVwKEYR{rq=Qn=v0m_UZDnrX*XW~C=|2p z_*!ppp*=zzJJ;#ITosN!^AatsVZ7+23{yG*)woYe%WF|>>MOviY8ttn25IX;5zNq4 z;gGIVxoypvx{hMgx14A2M)5Z_afOdGz#M5tQ4p+KxJlJ|Y=6L_2R)|$;F%)={yE_Y zfrEA%LT&i{sWDknJw1DKq}z)j#N7Gpwx{w5##Bev`I18sA4R+Y{jm!*N7 zR_)gyqO~Z5V|v)b0n}=t1kTPFdjhylOD-HU7*B6q>eAbt0ZTOtj6+SCSbF3hpGWW( zjVWDRBFRHyA3u2-`}(E2NI0B6d01G8LO7*T7m?VMFK;#2;BEtOrIuRXH{a?&2GMjD z?pqK?%YihHyf}1v&+2nEdoFQ>At6JU7u~0VsEw)o-n=P?Yt=pbf;{L}#X2T8!U%0q zg5A6HFbv=y4h_G#2Neqt-f_)jP`U{>=QLa#Jv6 ztAU~hoGB5>xm{)a_~p`?t8;+*Ymx(ds1euhqd`;3jO(7h3V@mP#ee@Hau_h(PwXei zNf$1G{^*y@TXz-2Us6V_|l(SLgAqDbzcc{*%1GABO?KW_of1;2r7oa%* zAud_s;lA>soW)c}$4$Y))U^c;@s#o}b_ z=P4aMsZzdep1Fu{oH%bwHf>!A#(M7SgZIdq5BfR*n_GEKM>ggH82LFl!43^_rrYoB z#%r9HYHjE8^x?qYI!2!;m`jdftEIH_d>djNB9|*6H%=D%5lEq!JFzc|Xw2(|0o6QC zk6P(-Gl`<)Lbz;lEI9ZFZ4F`HAJ6i7F(>l6F03V6-2zQY7(|V>R;sC9>$o}$E4MlONEil z;E@qb1t1UHGNoWIR3#GxA=VF!Ge#ZNHgc%%rbZe6!<-2lns0u#qEC_B8Mc`KjGYvRiy*4n; zJ00`s_AeYYnb##9I|wc1O?Z+uxOzq1DOz`Fl6XCQ|+IGgx(y)$+; z&ZqSk;ws&7tV`Pllz>01gE{u-UwZGkVj8W7Q3zPt=9CCU#cv6*gyLKDq~gR_SP4 zRgW?7f+I6O_FW=EDSaX%%QCO;XAjfQU`S44duSGESZSG5H#F zKay@*7?ucD(C_+83{PL!RYx9sVe1hBn0Tw^`4)ZKfa))E`8xD9?@}y|G^FQ!f1$3g zUqiU!YT?t!&E#z27o3JP+pldnrYGoL4P)x?#b%Xf>glkt7cf;)%lx;V4~tJH^KVG) z-N8;RT+p=aq@2>uHCoI;W4a2N;qt+9?=+vOeD&IlC_qeZs&?MW&pUIv6 z)35bQeW$u#q{@RRwqX-jUd0p;vX7lJH*6v*@LqQTHd(cj*AN!H1~5GPf>uNvo7SOO zs~nB{etZ9Y3L&U9`0{G#l7Cwg7YDY^QZ#&kesH z^m^ygo|!Syqr0Z~m3!Qsog!TRd)4Q59W|;n<&e!u{M|4|T}vlaKjs@=pD^6M=M?Ux z2>{ZK3??k)&UVLE;&UEuHCpGeZ);vf(D@=LhH&s|3Hit-ZdqhXEMw9P)Jf$myA zu0s$qTy5Z6aD<-iuZrbAo%tN|>>y|PcVm9OKPQ9&I2m%7@V8@aDYX)W4=)Q@!8S%hqqNZkgdMXzkYlBs(~U{$NF)kr^( zA^{FXVEN^NJ&hn!+MiWL2`NMV8Rfspy6{J1Qy#H?n8Q|ae+f)x-oJeE1K~~NH&Zhv zzv4M#(wu7q?nEGeQGyzGOP47plgljr%(W$Z((YZeNSk$H#vgnEX7&lhb!m&|u;ZAdsUL{d~LV36%K4ZuzY2UsoYus#-(viY|LT}kGkgeas)))#!>(A9| zufaNH{R05mLNndxp}x}wO=pg4$O~QPZj|p0apwaa&z@g2DFO(S?%}6ri=_q!n;u(D z{|(Sut4z&wP?(u3z(fY>Ch(lJBVH6!y|j2+L86XpuYrrni_2Yfiubt1+1&91?4W#4 zwLLQ+;zVYF_@j3&z=73E-x_Ky+%P4#jz+#ciE?|z6V@z!_vo3-M@t`;zNqJaTCZX7 z#OO2P$suO`FSY4^4!Y$V^RISRPPQg_wD-<4RBlFX%f(WY=`4O^SBHoBBTC-w6}hii zExao;w#`iTbjVh1)$cL`4hFZlhW6Y7-?`OUHLgy1c9eFC%pNriIdN`&&@8ht{CM2# zB}EfOEtw6T>`QLPj}bI0*Am@CzPw&4-rD)?0*M}SM@B~OYG-ZH++U`q~m#(|kKl z@;=nn`EeX4y3S#ZyqqTM%(<>$u}RX2yEeJ#xQ zhl$H;;IF{z`GadUwpfj}T(@1?ml=_$330s7`|_MQfuu ze+ik3G55p9>F;iL5dwhRWcpMzo#L*S5nRe|;__I6I@p{2IbBUBumqY6x~Fad9?y<* z^Ibb9C+ZjoE-rHvJGM^f-rS5n0^=PQOCJz{DMv>e(pT+!Go;&KN z3PrzjtQYNgEQHIXaQ4f(WTFrtWsV{4DwfI7%$(jEPC);kIPSvPZw1UeC?fx_oz$p< z43Ur1np?-&n2Le}fN$-vyejgq8W-l_5*L&M%5rg&l!e<;s_9m_Cf^yXQzPA^@*_~4 zqx+9yuvNfM_wjLuZw+||n_e3@M-2}TkCb3aWVpZN@(@Z|Ok+O%sW~}ex!PSRzq8!+ z%3LCA=cOC!&pB_Rm-+|pt1!H=o&oqoDu zHGxf(o78?*c9$p3scPCht%B64`g0Lw@WBzjU%GiqwbX~W^py9-MG(2+0V-y>eceMX zcQHXR(Iq{#*9%oqFwxXS%fIq-#_>Es@}-q%|KhA7gsc#g&rb89`~7y32e9P6*nUcK z-1O}}Y&Z&jaoeS>cb|0^Uwqu)4$$iI)2E4&^im!mXznM8n<^+PJ>7K^Y|t$I@TgJa zdyD*|OZUbizk~vJZ_3f$hY7pi)3hN$|dh!0&NiFcI`O!ZJ>*QT})2O+FfI%XKJgPLGuaRaRMLOv@Cvr!X4S?R)c3e zpIFeK@buh}Nf+cjpbVx0%>ZblTBNEI!81hyMINg1BtH-`Gdv8%XC3k({8A!c=yr5k zME!|fYM$Ar=F*6`saVRBVUyGwODY==!%ACxJS?>wZxr%kMAocx3O_D)SVv~Ggk^rx zCB^SkP-(xBa*c_t5o7DFOhuW*2`Pqz>PFrm`sh|{T{(<$6|+&N@h(EVWxVOAt<=rT zp>H4zXnK+N49p*}TW=YqYI&Vys3F@Z|BXC+G_0=rMA*b&os?GMyh}+Tn8Kek^z7K? zR>L19NZUQHOYS2?Zc#iiBl5@iAH_Q^k3R&@JTs8v(_eNWZr)^5dV`Y>xerp)7i=?U zw7s4tsUPQSNnx)wML}Y|#)Fi*0eNCEj`F>gv^Ss6Lm~j{HO&HUEhM(Jjb-lWfQ9u5 z7k{rG#o~DV_qnx+1~6R;m9`3`C#wldN2z&Z=9OD|!k#;~?$XKHJ}gi64wQ;u06)7LMHWr)(dSF-oC&A@rvr*<2bDWkBPMF61uVstnbcgt9aB|gQHrKjZli}v^S6B!VvDMy z6C3`(|Mv8R1=iLHQnU2Dd~<^3`2DVqV&jiHoDEKDW%F=NrHe|kzb_iR>gzrt-M}Zn z-@SZV^I5j1D1V&S8T1S#H92@=QNGtsEjh3&SvBvFcja&scDpWOR;{AS2_NqjTp(fl zWt|Hjr^8srYA;vEI;R9WkYvKfw7WhSHl-`H8#JtoDgVsM4kUF9z8?fx_PUt&7j#v9 zA2_s6W;735^%g(F(L)_=BV7PaHs@qdwqS@Z=2x{yZ!6tMg}Uupp1x&QwJFAdQ05}7vx zPm);Sg_HQZQerWfJ5RQ({gGl#dK6Y3eo})xTVEEZ7$jXU=+E< zE-_7EKnfn1?Zp_TNz$4T3FWFq86Z*x=B(N#`tq{(R!abJi4;)jkh+A@VgH5{K z8ZOg{&7N0&8HujAqmoQ$znKQyZWWy~v&c(ueJax6NGQ6DaKf}kY^@sPQ?{OhoQsNf zK6~` z%}HtrmpSQIZfy`b+f}5+v?K%b8kT^y9DtHnlhUP;kVlvARCMO+yVkS{tdfA(9@QXJ zHt@;w3XLFjZK^N{>r&n#qc4Sxw93fb^sLFy zq{O#0SPLcyOF9V*N>QH2T@nz!ZeUPU_r%j=p-YL+ZQ{`E%&yu#`Rf>uTt zAmY8Ib}$m9cs0U!yGhu>@-_J%t~(mWv_8@*61ImGiZM?k29`L?je%Uo!YC)TOkraa z93BR1TEj~+5J;`bw1tmKx_!TP7eDyvq$^zDDJY_mm{J;AN?;UGMFI*#OG!*YMHEmV zqKYU1b44W-!0CJ;V{3hJbhj|1p;1T-I&)eH;WQo~E>s;n4=@6HpYHzvp4CgicM#j$ z-I=A1Fvz&xz-O9<9}(O%7W#WP0^xT{yR3VMALq3_1lzf-dEv`jpAba^QHX7l4>CZU zXXYax=hCA~uMJIk{hTa71-!MJ9ZycV&*N7wHOoss5yV@(Da-6W8;pyj!S27M~S`qoESHc{UJ6GoRF) z{uHzw*&0|{N#R?2vuzlihc5ls=XXtsl~(J)y1WBS$GT|(N&Qn|Cf)FZiDv<_|n3ZUSr2L}UyDwMt( z(ybzvYmHbK%AYT8GyMK?Fa5=I#I%||7zwauE`LZ&RR z2R@m_Ot(?PX>j3@h>?g=dh=QHY7$-*iOR(mMs|vN5qEP^TWf1jtM(?9G0TCHD;94o z&i+!BOz;hEKnz7$H3fJFQ1~bFsuw0XubSZiFviQg`tv|Vl|IcdkIXSiv)5_GDqBq= zZ%Cb&)Gc1RhV7)9VJ=`GkVEiS(-kwN#PY!{teAmSdNu<3!Fm~+PLtsop!R?b6X7!}cdokg1_-fNlF6xqgHoH0BP zYM{Al=JGV&fyN5sj@4G;c;mNB4bIDeO+jyDZsId1rfNlm+(G4m^Tv3pKxMa>l7c#u zSH-j!K)lX8z&OPu_U#cH#tm1F0?bb%CyHY<-L34SQ-D~GDd}|5TL}?!o}#nmi*^Ad zzd>3yY4%%X%I61~W4SDE_ND{s=4?hcDe4V%k~o+W$0YTxYDj+1eCgS_70Fd~I%z(Ig{M7E@Xy zHZmxD$oT6_5i&o`Q`uxOTDNxr*sKTEv_zTM4EPnH3WY!jsG_qu+QVnU8hO9INp_Hu`IPI;{} z8cYM{2iCFRb2H1HnBuKkU4Yvjqzj@b!9B>Rq*;rHR{2M2oi?fZ(!K)cJW*kh`O3<3 z!5xiKVAn+apa6Q*_X0U1B%FKIOKF{0Mx2cGpb4V2*&sQq2+ord=jEp^3*hyqp^d>E zY1l(#M7oWXbrq3&WVdz`TF5!aA6nUHe5Eo(e+tbdyu!`ihJxph-`X{`)5OCB995k< z+jEX9cg8lTnxe>~hg_enRMR5>gIp6_uE#`O(a`Dk>WDb2`hWvOzQ_Fg$^QVLnyqR^ z0IM2<5A0j-`uCIn06{gGr$fl7qKak$8YrTH1r$;m1Qbz4FbXK5fdv#%Km`<0OaRhQ zXrMtw6i@+06jA`9iYb7iib`4lD58{80HqX^qpeWKQ^4YzPZY#CN99tY(nh(faKc@H z1Cn?Y3752OCS6YUJ9w0mPbbh?(Ri3 z9XU`yA6iAdjyKsU10CoYFOeQNEGs_s7AXeSEPd)$)nJBPE*u|vuGfokFU+bIfF_xT z*}+5eHwL3sDuX?0Q|0-1R|mgZjJP~xesqgM=9tumSd%-Yj2}YaR6_0z+4JE)_Rv(Z zl8a}Me4?P9$<y{{TG&X${P#^c#C~6T%{meqr9R z<~&xt(6SL0TZpm;_r-IQ-9}FnNB;nQit35UAyB%v%U5HK-F{}pWsAg&?%yB%^eSKN zKQLp=j1OiNqBCZSsw}D%zG~2N8UwiWth+M<9nU;}i;A^#G#*>5bH)fC-xZ=Hog5hO zrlMvlNMprNwz!4V4?Zx%{jpD)Xo$g{BmV#c6=EGT`B`($cmk`eR}Pzuq!MxkBTon* znWH3h`Nc%HNb@myqX2Xp#UU15f$sRvezd|(dy#Yh)-ZdG7= zQZl@QVQw>2tgj+v^4eJPKiU<4d~1Sciy!Zb3M$^K#-Ty^YPGzk7swgSS=45QqFhTB zbN%sE?fj*ccTExR_r)<6tjhA8>k85Xut2VN7~>AiQ@kNzQr*O zmigjhn{o|SW-P-sG_qT`++~YD?~1GEL|FdzKl|)ZBQM1kDL$SqKZ-i#?2g*&^||9Z zOn^v-BeM$QEu1Okm-DVV>)tvkY;-oSnzek{(XW5&-cS7m)@`)O$L|yV`W0^WWd6v! u0nn2J{{RsF>8uU99$!dlX(=4xMHEv2q@|>wK}8f$0Ywy35K%=G0sq-S`qBOX literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00005.jpg b/sample_videos/output/v32_01/00005.jpg new file mode 100644 index 0000000000000000000000000000000000000000..21cc55e33560191fc1b13acbc010f8fcfac0f0fd GIT binary patch literal 21113 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 literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00008.jpg b/sample_videos/output/v32_01/00008.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1bb6e788a7259a028a0670bd5c529cd98783d9f GIT binary patch literal 21142 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 QieMB`MFI*aqJTgD*~@gV1poj5 literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00010.jpg b/sample_videos/output/v32_01/00010.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c7583ef6c950684444aacbc246273bf0ec9c414 GIT binary patch literal 21168 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�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 W??8n#l%|rH1r$+0f{G}h5C7RT{4-+! literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00012.jpg b/sample_videos/output/v32_01/00012.jpg new file mode 100644 index 0000000000000000000000000000000000000000..88cbb6df98f08a95058b131a2254dd3b06dc88d9 GIT binary patch literal 21350 zcmbTdcUV(f`z;zm4@Hm!>CzzyASFSnf)EIy2M7?5O^1Xcp(>V5krs;d5&B?)mH9yLcwaVy%ZTS!2HM7~_5CKhOSI0|;QuEX)8b zEC2ut^9%6L3xElLi-UucgPn_$larg93&acNi{-E_LDLi#vDTSUYxQdux5NtC70ZV)&U51?k{;Q@9+d}knkxHQ85KYC1ofK zfz&>$qpN3PiZU}tTVNaoDmv!!mDt3aNy#axY2@6z{DQ)w z;u1<#HMNFTTUX!E*51+C)!oy3@9~qtq2ZCyv2n)y!s61?XU~^kuWxK_ZNGWDv%9x{ zaCr3j%imwW9nXt}InVzd|4!`xnwKDRUO+ZBRyNM#d9eUvn1fZ2ja^2A+gT&`#rt#gYYGZJCt+F{w?Fpg^q!Z zan{4S&>H6#9!?6jWz*JOUAiTTjfG4evFv$3>IBt($&3~YO}`Y%;W)ZFR>34 zI%kNcyIeC!;jqg4;FdEa;a&>R2W45?dtZaalhuSzJ~fW#FbzCcMOURiFl`n~$kt7)8-B`IPbZJq2+zez&oHRmEMEJ~@U%Sl9Jl0pvqrh*qwKY4s<6Ix){EM) zuZ~<@ugRKfS0$X~es;M0{DnIE{SvR{O6)A>MTG}4ZII~l2Tr2zReeVTvwuNyDPuTs z>Q`&s&YS$YA_zc{hgf^Q@U2bc^sCFUyRRZ-zor_(>A&V}N}cLlecu>1Qd4Cp;;>RAoOPLuDOgCff*TFbS(24` z*unPbXpnNv5Q~>`O;!9eC`v(<^|ZibLRZrUTNYe)#qVA0z!eDZb(?gXE>6ynODN)f zGshcFCAFT)^q!Rv$Zb`@zI!3xtM{8G>aqj2P8S4ON`tO6S&qdi#<$(9G=6&?);whH zW!kp6Xrb_hW}-*{M?=IhhMZeY6dS_*oz(U|i?`6pjZ2s&KKOYs3;3h%8t&rU_*q7? z)hhAk8|7D+4il{@uo!IYBw3k;&bLRgC4ud&*s93N0!$S5M=@tm!8`%{p8*9N!}~Ck z1NE!FQ*={RVPHbY{Z~jMd`Z4+3xG7dirqcYYli1zstb!4nWE;b$9Wa1x6# zR5re)g4o#=$|*X)1ye!<*B9oXV~B)WELPbOqPYNN(C~o#NU9I!1b7&cjG&k>@(?%o zthjs|7@)T8M%@al<|Dy+k7^>D>7pchQMADXq+C^;P@YKf6h+q42oZlwo>bVl9}SMr zaqBD zEwrIRPLDqctGjf`Cmu60)*O0(b=A}~!Lr(XauIBi>P)*{KRN=R{f4`i*S;x~<5W^& zruB8!`f*VApAQx}%lk7Z!A*T`iB6Q)<`WBc>|cu85-F|VnUjg-e7;RzMIh&wMTUhK zaY_W+Vzhx+kcNSOW69-hs)c&G*L-<*5UG15nLT`7d;_0za^U*}Ega8P^5P#FzOWkJtC(~B#g_xWsv9<)b{ zg2#1LBc9lX+*^MpoL(=U~G_$wsfS@R|(wUpsY zaToK=>6EV5W`tA#B54|kZyFSD80G}hjI@82(NTl14u5q2Qe}fei9cDnQ5wU1&NrDT zlvl1ww7PJN&Ex3$AAJ7CoFcQiA4u@TnI!fh+$WBhY z%$};2maUASRCOy-h2HeZLBs;`FqCb8&K$3>?T&E(Lwb> zAJGe4xn6wu+B%j)ecl>MHB<(kf?WLORMaqIg^GS>Pbp~7gt7=Zz|87)vj?llk7@`E z;#td+{Ccg2V6NL}hQdNEh(qr5l|Fc1dlz{ak_-Xx|LQ0#B)PgZsb_IIyu(LF--z(t zWO-TecqrZSQqd%7Nc;09EV~cpRw!uRYO&ticj3n%{}q}d_R&JNuE>)UORZ{mPlhHE z>dt&Jcy8$fDN!9Ga-I3~Vk|Z}X!LP5|Ay-anb2)YkLna_%bSu-1B!p3r`C{yx`Kr^ z<<7J_$#G|3w6a1;v-B_Jlc%j5JubD-iWCdtI@(X;TX~;(25FIwYPm&XYQ0>Z@{3NN zfW$uke3zs5wU*T1biQ*R>_3lQMzB1^pWLNVo_BdRkd}H=FM&fqm+AL)yD2_q<(5(e z|0IQ*gS()Xfm-tfm>mmEYpQcq%35!&MKo>N@f%JKbQFReJM(@sDMe@ej){j?1dp+n z*%NjfK1EcMQ^kP|V%`ob1ik^r&a#qF`rzdFG;74Ji4}Ln_r<%x&w#C!=aEFWZj6=Q z?XX7>A6*DWr~ndj0wO1yO{tgN?vONwT&!BphCr+_oRBkx8rIURR;E`Y&~^(cz&zaw z(6l-MypT&yub|1~Mm0G9v3q9+ck$k$W#P~H>wH*+5=VRE{{cQIpQ?feGpt(y$;uTb zbyeit$ILg!C;xN!q+p<&7${g(v>IWjK?o;wbfl2hFTk8`Lp;bTn%~?(E$z4PV`Jdg za{Px)9OI*W2HHVTHLhcb3NB@f!Lf8~mM3u~go0AEg3>X1Sfp&~6ymT}%*05b&pEc` z$aN>4$I+h}+9^t93;7p#m6hI^HQBNmxnc0x$Pit`ccVx9bIC4G^+gscZ2W= z!zp|RVw(ZI1a82dqyOV8`)G)pF5>t<0ENaemmJseI>g{LPyld8z8z~abR*H;u`XhO z+CSUc8@OpV0>4;Z1}h%m=#d{UhgUG7cp*vChI|P57{PPcCYcS$g=ZgMBo4W20@fTv z-W)cc8tU?fx9VkZzB_7uKE1v6^z3@gD_@;0YhKaEo!V5{Vw67@1{zr5jTMVR%mT4u zcoQq1_3w){p_d^=r(e=7J*x6nte$sb9i5I+H)_lSPLINkjet}?&Ej`fQR_{UMh#dC zG=~<86E)jE&9|XxVbDr`3QcQNb6AZOrFQlVMJs8-&4QypOXG6Bb4(_9%z~GlLo(pZw3kg94I4%)7FO zs^U?sybOlD5f76M$RsIy>oecGxAJbe_%8K5xE>YRzEjl3Smky<=Tbo^0V85d!(^F6#XgC-Kc0}E^YZxjNNT@*qL*!> zHQGGQx@{&uW6vlSrslDdpDbBWrkxH&>AIUdr~7<~C%UGsj^I}Os`n@v3z-Y^eEtaO z?J}~lC7Ml^LO7f^;-exulIuM0#CEFMh~T`hzsBQ!)*B8^m(QgX!q$kx9r;O+irm6_ z5%+FLnGCX0`jyjWdRBhiRH(H&xnpkakoI1bCaZvNKMm&E>GzIPfJnPH6ZxJY^}3wF zgN{8Oowu&_q|LO_d%rIke*HP|lMgGTUJoU}CYf>K|H4ZGGiq@DKkP;=5}##bee{%Vu%(ZyIzuKLTI&6(RIq}! zLu@3pUjW3z7`NgS0OoCeLLq=Zzq3ZfW!yzF0_}oR!cJ?6y;+RWk)qw_)8jrVFL>8@ zj5?9l7{Y?{&>X%OIQ{~k8@t{bxwxvMc^27N$L8i8u}q!aIJ)1(BPXD~IQAYpSWdh9 zE{3i!QYgN~(dKuD4M5B&(I4)q^ycd{->_4uiECUU+qfKpKU&}Xu8P15PV3Y&UR#Zn zv#Wf;)P+IGLw<=7o}faV3rAJlCkp?3FmhO5!ab}ENe%S|^)@J6*};9J={$=i*KT{q zNtnS(Q)5bA1|61KV;Z-4XoVlNNA5Rnu542B3p*$7#(dDOUSXIu)AI zmKrs4toe9rSQSfK+a*|VxD%?HtArIUZO;fRPL49UlceRF8Lbjn(Z-s)C7P@&Pb0Wp zKBO43hvN-fPbSO7!7E}Sq!wvuCxhY`*Lsn68Xl@V%X>=h=r_BarR5UaQBwsd-8`gm zXc#cCcWMwzKEh;Un+i56VwX_CffXas$~$I92G0=}x;zTXfSXHGrGBXuwh?9wU_S}p zikFD2b#aTUSnm`z03{~jz;p+vM^ff$2*~cz?&8R#q9jAkY}%J2$9HMI<&6^ z;^+I+rQ9g7V_8Rzj}SF~(_{`TDPs@3z5lFi3`g4c>ZbilrZ7`hz8Ks@y<*P7hzT*8 zG$LQ@H<=#<=i3{ds3OBGnQJJuu!LLHHwW2taof!#e0JBN}69 zl_z%AXLJYu#;SzFO7)hP{m&xK;uq&JF{j{~H^yhC)uFigGjVW7{0e43F0=KJ{z2}E zW_wd!Gy5qtDebYgwdu;w z;T%%*=7`j(PUmyNJ=5sBX8ZOWfV$OFj63>D&rA=#%uzc{x-@O+zB7I#xdN`B`I
Th%B58mp=tHQi$K@vYcTZz!2dtvwdkccRMQ|n8 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 literal 0 HcmV?d00001 diff --git a/sample_videos/output/v32_01/00013.jpg b/sample_videos/output/v32_01/00013.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79353fd61d53ae1a8323f2cb891176e501f36185 GIT binary patch literal 21426 zcmbTdcUY3|`v#2SUa5gQb0;`*K&h#@Fu;w1f?DnbNAAj&BP~hIJDB2oj4dCDa z065rxfZq!MO8_4a4=)clA1^O2KR=&<5J*@^P*6x({O}>r5t*a%M`YyW6jZd2DJW?w z%gL#qglOvM!eFqY#|%vj^o+IjVR{E&!oknaFC-`=B`hqZrzoeW_y76$-3bum<9G-J z06CNZoMIe6F^=E8fTQgHlbhqu0r>a9!3pHz=Hcbz7Z7CM&?pMve|sHz=Phd^QQ z6DJJ}jV!GY)@P6?w6hBq=jw(h`1<(=oDU?DBBP>XE?&AEn|L!R`PS_ea#nUuZeD&t zA*H;6N~2d+RWlydH#9aix3so(_w@Gl4-5_sPfgFv&OLwea{l$|+WMP~x9>K$KJ9+q z+yC1)PA)DW7w^HmI5;n|2T+WQTTz!s+}w%RJN&Sc zUOXSjBD1Wni(gqE^B34BVp2dt1@>C?)4|mK%>m#( z1~?7)%SgREv&snp0{owm9GjoCgIO?ZR<(XBJAQ4;ru*jooafeaN0k=r1es^0E&pzX6x_YM(#& z1eL+j&}+iZ(OnO4Gq50yy^v3FzX8kOo^5&glCC?9{98YI8c$^pPmYVBFm0l-F8+a1 zIha*4XBSz$JmWpc$%f3??PP-hKE#mG1Rhh}d~m(Llu9^WRH);^i(^Vtr^ zz)!)9ouleMnfi9G5h49+fi*7!({`pWEFI1DU$TljfX)NWQs9{PvaMdAtokrWK5>`v z-X#WPqiyGsbuQNLFpPXkAdM!Uz=Y+DZ4}&b?=s;wEcPvB3=?fWk-ZS7(l3GYqaxDw znAvd07DMarSS9I_z|u*aS&kDzmmdLgM6wawiB>N!NFb}1^0H6J0_x>)|7&D$gPhEs zA^v(leD!pw{@`Gxhp5~0Q#6vrEBkcKJvG*EK;TDXN?zLGiKw?5aQ?ClSHm$F#j;g) z_B_~o%r(}`EOx4ra7ZmTc7p2wzm7E9N-{!mshWNdNdy@}{i`r^XESjDYZ^~Me@1^c zG8S`qOcBb>+`v_3 z-!vyaH9;*ip*+ubLSU=msqhN_1Jg1Mcl4xI!#Xd=#)sZ0Ru$`KPu`|24(h&8#UW<# z;32cmxA{((a8_cOo)!(j_A;e}Cb;_wOE_EK5a}^5c`K2;c0!U22+mQcf3F2_V04x zgzGk%-Y?K>B%SFE`6TkBduDoL67XkvrM{@qjKsN?#~cpul*lO0yKt4Mydyy;aHpzx z`Yx)>SZ$Z?y6R1RCK zD?|a8V#mv+0<}6s(0;WlSkm0qQf1qv_UY{DiP?Nob;OOR-vCROlmILbS@?f}4@o2n z>#_l7djK{|Hu|!<{}1vG-tfJJhVY^xAT2Gmz*3*7^=TqeWUVI?Q}cqc=j5F~wSrA5 zj-9i&GeGuqXmC7w1auIl4BI3a&E~XlMN@eaDECOI5sA39;3YweTcvuL!ywFRK1e7;u@WN#2vMX_Vg07ZI|w>4HG}``lL-5ulGT7QMppTQnlneB zr_}#3CkOz*$_TxZhJmxt(Z9iH6a>)k1#QR@npDZf7r}bbDr3k>xEr?M7N}Q4o77$n zzH}Tc+m|^Nr}u`Z+T-X_^Tb^7C$ahZ(0smEXsV6AIt(N8i2a+>71hhh`~qkK+J_x5 z3l0M1Ehhw6HHrP=;N*V~K?EmRfPGg+UpZM_Dj+Y53?M-CZIIq*sr0=ZM@dafL!31H z!3DJy4K6vEnfj~os4~jPR5I0}-Mm^$2sUjGo1ThOB-Q>R^$5iG0X3Ql-qli(wn`ad z6jE6LAR6{%+cm1_Lc=Qs1iiMM z+X7WHlA@sCz z<=1O`%80;Z^A75RTC_x}ah)p=2d|;3v$*o%%7S^T^;ES6KiRE@%THNvN|4d&9D!8Y zpK#~Rj)9!B+sAfXNPSEnwC#CI{^98Y7aXgV6a&_5utF$;emb53CF8=e>r9M5?P`4P z0!xRS**4|Kr5bd@;-*S~?bWe9A72%Lt-V1{MYHN`a#ne;d#6jacUG*xmX@RWh1Ry! zhYkBgZP1|E0BND!tf@D7$2rnDckZM0raN)KEGX6Qkh1Pa!u+pKuhy%j#=LDz0i4y+ z$lNlIaHOKF*$0xYt~ZY5(jHKFLM8-7|CQSu#e72g_F7K|GVLG(>C<&9SNQgj+Xe(U zOo#~SD#a<4h*k+Z`VX-LQ=VRI*k_#TwGUr@p`Jd&dBs(d=AKt*u*Ql$OLQYwjlq!H%-p30IZDM9 z#UC2YjmBy`UsvYNR-Td#kX|YFDUi~P=$&=|(b+Ld3zTpV$wh^25leY6GU4=+hKYe* zQ_^&^?P~ps2W7tgPB>?LG)zk&pr4zB2sGQE*2Qsw>2)hE!piVcs1X*Q4SrzsI!I~r zQy<{`Fb^@#NjcK?5Su1_DE5#i{?^aT4rz^<`1N65jX|zMZ<-P#DMKB>z3f1Rdqb*- z9Cg@(O==EP%gGa?Y+7h85{kH&-#Et{Uk ze)p;#3q@kal%isdG=^G}!Tw!7*UoC)-7e1x6&Y z&;P>zWOTo@?b+ZdsBvw%al$!0SL`nNiRSP=PyYL!A3Y#}etb&73P#YmotH(*UDz#O z{{{W0u>9FYb$?uMur}&I`dAXYVR&(@33|&bxGRAd?VjZX&0xBv2KY`xtrK*AD(_Z~A7|FSEv(I4*WLtWZ@PQ{ z4?&sn^BqP(2E}q!HD9K*x{i5`nMQbu4BWS*+bAxs#5+)4SeS9P6Isemaiut|U!`3N zj#QgUuj!lN3(14ar0?x|_gcO`hfa-z+Vl4z+xCVeIuo}k4Wo;8BZXMekDH zoDn3mP{mr%=2ld$sPA-gwE<`-NjJrhA)ntkPRL66K;sbh^0dh!jrGmF|BQD=Wf99x z=q{HZC;K*95eii{2%)41jBaEXQXfK)7UY z6q=}hK$-_x!2yFhDnbIw3q;DvP(e1{*=cL(#6i-)$0kj{!FV~{{Y{W^{KU#?{p%Lp zDiA8{S)Y!QvSzqV;wC}HKhYC44S)QPbWCE{+3Ptqr;0Cwa%mUoBcxuvVl0n`BgR*q z)~Bic!qx9X3jt`!tQz7DV>(&*B!FB;X;vlU+dH0r_QpW_wNXR!$HOBB7l znzyv5rp`NF?o$tqtslTsZ%dYr`IXRFftOR6Pb_genglb>YL(gWzAoOm_7IsR<3?5M zaMvq`r50KAO)c@CLqn`fQAsATxN-$xytDP=nZ=g~CBCRZMU?q6!Cn;a(ij^bHThgIu)a)66{p&ml{G1#vq~jbGyH)(~1T8U!Fp|H13Cgmf8N0AEjx+%(-_0 zeWt?$pOYzUs&g{Zju~cll^z2Q9?H}OO%dI^rhKRt0t7Qipr{O57zD^+ey!?LUzyc*5<|HQ%UUz?rOUGuByE;=ApbzgjUw1uhMJ#IA2t_9GAqr2<(lyl~?Vk~rkH zH|?yQ)R>{m1aQWMtd4-IpM}H=l9>@tTQhzG9+B1)m>$Q!8{hwM|BapasF8ZOeUSIlUk!1ScD82Xc=Bhz2=1{vlV43;}%rIWG3wfP-Ne>pbHuNwbkzkGDO4?zKTK zRT{jR_l55-j^@U2dGPeSfzykKpvm{ogUy_+t6>WSf+eB|*PGi;dI=uy zevyIODT<;FTgQ1!?NllZR{D|?t+3IIDS@_atRF~-(OpQ+t)@EaT|0dz-VGlGNImM7gT7cXsfDbA>Mkb{DGTKH>ALHFBp8_v<39;;Mq-Vo@0XS}?z zFf=El{E}Hem8?v7tNr@?+}DSl@L9J~WHNLC-n^xO)(gkS zEFpp{ObK2#^Qq1QXb%jB#m`2e3QiWs?(AjjK%kL*!>om!jnZfPl*^!A{tKC7s4q82 zO}BNR4#&xWvNt>Qd`^}_5;$M;ma+3;mCMkMo{eSme}?FihE;SQpFgV8Uy)WiTgZ0# zod2wu|3R)Qn>hEJ5U5f%sN*0IA|&oVq={y;sH#IdAr|pq{(-OU#hSr^5zq5Ccj{y0 z$Q&h^)E9iO&3Mh-Gk;PA9(Zwfo8|@q32@M2}KgNf) z7sPuArXKcumU2ArOn6(%MMGayzPLV$A`D8_0_8YzP9i`;5ydhu>aq=Lya^|S2f>;Z z6}Q)2S_Z2^zB%E{R?8?#?c_>z7QX}o*#N?L+bLE0R}D{a`6hG4z-KmhCDR=2H42n@ znj4<0aeY~>FX?aiYV_ku;}1Q$&FNCcZr%A)j1UvI1<@)=6Za0?3XT+bqEVLN7}})y zQ;XbT@?6s?7lB*YIH~gR*>i9u;F|P6|L7{tt1x_N0~ z0lfF-+bVi|UDn^(VckId=b*@wpJbMez~v%5Z0Gq_Xavp{t0DfuPnfV~xJbGLzmxT1 zkrXwx<8vAoH#-=z!=uBkOCmg)sl3Xwj$1sb8mIQiCtb(XiCixYB}$~-%*#U=Fv6dr z3;)^S2EWZu(9rghnq?;UlkPNgQMiUbxAi{RcWOF41}i;eJ0p|S<>=;D(%stJN({~M z(C2&k5NQI11#9zJq4FD|QKM|NDQ;WLHi(Wb(G%#Py(&5|@B9Mkm zsJ&I#QcXG|)h?vQJtG`1bGBee+b*ik{N@IYVpTf1Ll}R2+hB+_RM9;bJo$l}2(*Mz z5k6)cY0=?+1Iv|%2t9na-px& ztSh;&MM>LOK0`X{I>cbx**M-6ZVmFaZ;iNi!@}Nea#Z{Nz zWBjZ1?FHu?!b8GaM_kLSn=*TcU(*I@ol)$z9;3(wxFA1kh7IA|EIPn?o? zaGcr_;kM7Pny@$YnQ9TFY>g9yGn=^{r#6*y_cQZ#RrK&XZnLUhiBGB|dEOIY)DCy*7+M z1zK7(OpNIM6-mBVdqUv0mTN2SMYv0RZlmzIVJ@DHhxc)=!9$SmKqpd|$H;L&yo)<3 ze^Ae>$!N6^CAXX1%79<-_v}8Q5N79|h)qTazbTH5cau=F35NyfDxurM&RA}HKDY&2 zzl;10K$_Xe#UY%@=+#O?G)}R(9d{w~HvmXLsjWb1{rAN3brUM*S@RYPF?uCNhaJan z;?ur?ODm(IDLm8UR$w?8p_H_uScfG@M6G>d39g%vIvy9!^K(pT-M^?1^`TK;)w*B?og1^NRt_7;*Q9aYC*gH3c z4+ED(NDS1*(EZn7;|F&x`Jv`{N$ z)Wv)jJ_^j;_7fOPOeB06Frah4^JNmjCzkZM&mvo>dP)W0vyQs1OP3h>;by4|Nn=?4 z)=#d-q!{nauFi@RJgahxuBb&iKZ^jz&RKo_Nb>4GoQ*u5xSQRD%`5O49Jmfrihq~kzRx(W2JM-J%Bkjzt94-gs^ z>b6e45|_wXD+*X;8XY#rsXsi$=xgojgXe=Xi%k^N+UzDNgS>uJ+?sb*UFfu5D^+h@ zi_&yY!)Vzyo}il%Zr0aH9cV=33SNNXQa)k*O-MbIYDg(U`WayiDj#+T{nU`5Q;@dG zk9t`WZYMh84i@DCJL8J`=tk+4PSuOR$F7Dr#; zoiGrFP|63Ld`%P@yBCv)cW#EWVmpCuuHw-fl*7>RS=6f|P<%=C;4CuFJY4(Ato~57 zS|~{;((Kj)&KybrQP5dbVk0x%_X6!?b*0!3`gdou;Q+EQ*h=?+fU`QMFjLN=Yy)CK z0hCjoG5yE=`p$O0W)7(L4?X8JU^v(>4$O-f62A$DLBvOmnv)@LgOQ`Y@Edelhvmn4kaEYK8RGwk7d@ zf<~;9gR12!p*1g8>vLl7f!=<>F9fx9bWB~#dcNWD^V$5vNeSucRAAqk!`x?!$tw9_ zymI8xP3zmE5ny->PfdRT8vA1@MJQPMr~t zk(g7#gWx~cqT=lXQJ{CtM(MYt{ST1phNWZ3@6t#=h zBBz(20Uoa3pf|OtzecUbD+f1PkM&t@tVh%9r=+srF1huX_F2@q<#;{Xh?mSL9)E1H z;|$BaEj!W0JEgMF*ZH$Xgg3dr6V5l6s!Devw&0j8EDr7kqtXWpjhd zzOEhZ@d(>?^`(Tqzf_44h*8%EIguR=$_seZVGYUg!b%w;#2Jn;PA&Ja?zST)b>z3+ zD|55iYaotU_mN$4;W`JKot$6CPHIgq%w_byS`^9<_FlAkPHBu(iyZT}Um|5{rla&o z4Xmz8-66*3I?k%BnzXz!Lct=17erQ3%W&353+sxjZxp}UDtbrKc|nW$c4(?9&jscf zNVgQ7L=zz?No*$5J0LI%0b%vh=>t4*nUdA3;r|Oo88^%Gvi=zMY+}ob*I`?$69;Dg zAJgDJ&jfY07Y1s`X%LopUlg1>^Ww=mXDgq3aNKjdN>pc+zj6RsGSdd*RvM6HzB?V? zr)Sv*)DKNIuL+{9%=L{#*Ia2@ncyLAyTV5LA3a8KtQnj}(_^OuV%oIdR$2;-ra*O| zYYOCtd4orTd?{~!9%tR|eqn2hJFT2O@f@SylDzFPhQIfalGTa}oC9WDItGS`i$K2C z>k%Ian9&N>2V-Z|J!%)fmVrX7e_{uzo!fXPR3G8YMxk~25QZO`fWq)YQk$Ppc6Mht zHZKNb3?F`|Cr24=ozn_A=baJCd_MPKH0CW&{cphQBMtXA3Q;8Ax{9DGejkxkK9fG| z%!lD_H#th`W~D>wuOd|w4O2W}ZQWm)V&*>(Sv3-dG_akkk&k@1^sI$BN4ccVF9j}t zI3V>JaiS*&xvM0-ZF^Z4y$gW0hICV?}b8!jw3(?vYQhOHQB` zYAF}(2s=b6U1FkNvX#Hjwrkmrt*})Q*)^M7+gd5Nnjt#66l8j~u}o>Bdhjt;36D%C z@jqvI@@Hdft@UlKuS8R6#uN`n$6boYn^nl}b7ZBXT(%}LFwwtoRic4^IK`FWlu zw4XXm87=;7rZv15K!)B?C0oiSJ12-BF1DF=5U+94X3C{!d?TYspIWyrQ(qhMN;9L{+2;gYTzHR~om{N^Gty-0tWxmhDl_Ok`t^ z2ttQ3V?hWSm#pqnn$!i#r9;~Zo$fJ{6xNAqkfrp{9^-he+>YCLV4d|y2zHIL+9nKC zg7ABGG+J!`vJv{i+$|CI*~$)%PmY~cHZ&39cdW=jo<<*L z>6IijT#@VD#>Cqqs^h3Bhe1B~$R5sON4T6nArYL)gCj%QUoJSEL7ny@A z+-%M|$V(2QTs*fTtvv2<;{kfux%nT=0dm-1b^t8E!Jz%S667Lyql4fa4KF&qTq>~+5`Yl% zj-xrKv_1v+-fw^yNhbI>NL)BoH(7dMs(R>}@otK0#8}BbrnvPPs0MS&#PscWGw$PZ zqfhKcLt&GG^=fl@aOzyh5yy+x>{{N#3b^$NX1EE8adcw(=Sl^|GSLYoS&$S-7F4Eu zQ|8Tddmh`VcbT+yY?;HWKpFhm;-$;j^(%#UkV}Btk}D5%U94M_ zJI;G(oVpN6Im|B==GGTj0X$}6cfsmMsnZS~)AY?ki`1Ls1Dr3?^)Xy6jl>Lwk=joZ zgkBOZcGjN9-oIMLu{qZiEtn}69O>~m-7ll=qbtbQX-^t+X0D=IdD~?aPlQH;xuT6G zZcBnnF=P)%VE7IaN9%);0e3-DjoOeC6N<`bgD^8uHGe z6me{X1Nga{N6lYM;C{`$(>=}3dd8XH)X&doQFWVQoljfW(sPROjVFDO3*?7sE@EGr zrf0CZvB~KzW1h4nL8;iu&#s08B$3*%`|88}?#fPc zO8T1ZwS`iZD_E4m)BdH_(nh7ld=6eG7wzMM5bXMNzFm94&u`$^R|v6t;}L;}Gi@B| zRUZ$4={wqwcAdq*EqI{_Jez z!`@sP>u0N|&R5XLD-rkQM3tk;-(y(fI+;Dw(=0;!^xOK}EVsd~ZEQ{?s<6o|y++IR zZlfE4Uj@{VkQXO3Nr4D+3P9Ay0Coazm_Ad+tB-c_H8rk}n{q3bzt{B>XvxU5Z%$Oe zC3K&d&^O5j+pl=x8a}Pyuxy$Mu$!1u{WKnQ_VxEUt=HSQ+X1kjx{T4wO#R(ie3Gsr zJuAOMRY!CRLqYR<|A|^uwlXV7NJN0d*^%lXG5b?D0lq6IXPX_636)JY;$(vQAD#5a zyv}KGJ~**B;3M%IP9(@BBXzieFK7NHZ+t zV~?@>qByF&TPGZGjxzCpR5jF0*2E6JtMqb|RJ+67<&xbOeO7j29xt)w-wpX@{?VmD zOZq{j$eI{r$h2{})Xmq$3;+ZOv{*7BBwL!KY@2`|Q&7jxz%-X@H^lZWh5l%8r!jUG zC8OrQEi~8sCi_D(Y$0er8&E>(XUTu@TnZ<5QI?S+dz&7+grdHC#u4nEoPza@)*w&Y zYJvSa?)jEMc#(B=QD7%IH+C-ARe7#(-DA@mTrQmJU}`h|$&1#k4{AtNx-l zgy!f9_p^AN7BhPTlRo#P(b~&KLN42w<@sM(yT~h$)e6G!9H+UB8K?o^LqV8kg8-u# zj%){|q@!?vXT3v#Dx2JGbCs%~+8@hWnMNjuT_2=2)b~j!A6e68Be}1HKayKY^4(M?y~xx!ZL-Q2W@XUua+1%vWs5L zUtBWsL+K2u3OwFzO}??{_9PEuvLG}5Tbfe}&Z?`6hqZ;Mc!>G3=9l)PwSIHHYT+dM z4P|Ee6gYIR_JQb64Flu3XX!yq3%-rA*6Fntk2Rip&4F9ke|`ga8g?3^q_~+%+cuX{ zItLkApv)3}P|d0O_T*njBBZWIpPsiiB7g2{Yq|Z`2HlgeenXlz>}t3KpZY#aykL=F zUhD7!xAK(^U!2Gt#&|)lj$SdssvzKN7|s7W1K#G zA@lt5D1lK^c9DZce3s|&h588f&z#-kz@3KmlQbJ@@3X7h3Lobi!RH|&FwB*kjouDy5F10^PCOvWS#WM*8d zJ&&KH%sUwA7A)MSoyQz;T>UOp&}V?j?V|PhIObp`&4do{4WT-JcI0Wy+SQ0yT&)Xs zKtE5ZLsHKjink;FWi%U;yvOaHK%<@{MDfEf3A%zCD^topV;TdOItp+-ltQT;E)&koP9|5R7vj@Y0O%I5#> z)}eB+t1tiNSawD_h514)^=3!uKS_eF4Fbdwz%#sB7}l|=7<>YX=W&@(x(8~&i01g@ zkpTc&DP{sA013B-X>Zotcnx(FInMmLfad5D@N@Gx1C>)pSJeZmy+v8cv+aV-r3Qm!)Ja2 z@{4Z%287#1km0y-w!m--NRP;QD!@Nr%W>BQ!V>=H;S)sMocm)cme z?}4`fYlZztH&?#EwZRB{n?=CJEC z5CC{MPdLY7+hq-ptG+0B)&aB8BtA3~9E;dhN;(yD(KKgC&q==V4Y#5@o%CjQm_aWX zt!~ch#D>U^Jewm}{J42dILhLLAp`w>M{DhcULvJg1EtyyeK3kZaK4wt-!f6ZI~sHg z^EcpXc~f1su1Z=(gp??0gplIJ(dj>6>8fg7vTWvf!Fi6^Ii>{t4YA3oQibZ)5?B^ed6?eU?p?B-EoleFbbEDj8DcOgngo!2E3S84tVs}-c) zx>O?bYm$cZ87H%}D<}Fja3^bQaGQj{88RY2i-G)BE%(VONi$Fy$a$Hu3xSvWtkEo0 zJe*YvF{uKhEg#+WD+z<`Qo=5`1Dx*{OhX);k?deiYZdInEQ9jncG*py5md&ACBT{mQ{sfv8V!S@EJ#q(0>s9UE>vEsnWtwUS!-Kl5_7C!Gf8Ad0mBQ=epMz6N-jZLWO^skg zk*jVlyS$bjxyli%?_E;mBA`*L4RCN%iT?>yWBAud$3|+M_FD_ymfC=t;DVGfn|Q}6 zB`Q}j$IMKQv~(pCI9FSFs}bU>r-^qwDSrhk?UnbkX*kJ!(aC#B%uc+;7!5J>L)WP>)Cjk#Lfh#U6c-9wva2Z)pr8>cf*7C+c|l=S`5RU zi}c)~5as-aM?~#R6%44v-UwG)n)lu7sISk5OYj>W?FnYw<$U#v(KRh0+Vou2c~8;x z;&AtV3B$LTbum8E5!mxOoqJDS@3GQ&#uOUGfq5Bk;rT3*M%AK{reb)K;u-KXagrKS zr)H)Q#hTL9`lg_huk!F6wC6oh^3ptVg&YCU-A49At%Yy8#Al$wn8#jDTpj4J2oY`- zku6!R@ie2m6v+9qEY$-tYJYxZ)G66X6`oiRsMxhsOnRQ^RH_lf5!&cy^Z8AFTsTEj znKJuRuS}@vZAnY5^(A=;=$1KnYj>yM{eXO9d`;llSP93IF*V0*YHU*5ORmo>M!MF% z4`|G&xqYU?MZ@EZW_|GDT+Vs&aNlsZTah2`qb4>{K-qREIUcTGm$+ZY{JNe5FqaPr>6LaY{|m5hyFMLe>UCRIndhd^8+RQKYhaj5D4PH7ND`$ zSU$)nPJjSW=jf?o{etlTaGd+BG2J>c*ND+g;K(uX=x~t-dHZ;=6f?hNfI{RK#l)kz zwIQjxHe^RgxLI!nh~1#XW2F0;{BG~fitA~CpQ~-2a8oDjk^W}wwogu)?(?;m5rdqp zd3VLDVq>IoJWi_H7pT^-iu0HDz-ETX#90VUcV~pB;>fDtLV3-CT@H;gMoov5BcUQpHXOH{7 zgN7VM-n4-|-x`sFw9C^UvMzksshz{T5S#|rYgdOy?Pxw)L35V$%xBSyc(GLP#lI7s z^YK|4{DsPkjuJl#SP@gN@JdS?y7PEz!IPz57;5wL-RmCqtLKS2%Hxuz`)B%$CZAqo zc-&kGd6zifrYhC)Vm)<(7NK>1XGqKgS*fB|X>m%F^!%2;r~M%9AB;^*_tS_*SM(g^ zh}PWu(|Ok&$HpY`n+~T?n?}7w42_S*_H}6q&!SF7(mR4oj(u%pr z6_SphKZfrdDVOeIxdm|(xq+P?9mJStzIZNu*&C~GH*vETLd(Y{wRkhDlV?iep4Cs!wUIPxZ)xm zI5QFGY6=fbz_C%U*TH-&X!FUDr!yXWD_&2RmC52yI(=>)YeeSl8h&%UU;5xo-CtS_ zigrWVOP!nUb(<0~3{Fd#wMuY9U~?<_Snhn_^N1ExC#S6-dfl{;S*RDXHrp{Bl)93( zJey9N`Ps?>tB}!si5K4V6-PSuZb*)6;H>(x>JJr=Y3mYi!R0AdDY3er1yni=yN`pV znhiHGmu1hO8*YVXJeWVm{Tl#%f#m5d^}?RwLR@=>(+(XdU`j3>A!Tu*R=?q3aukX> zn3I_vZr2>Cws|NtfrXI1*VWHQm(*xLmzG6vyoVySL^CGWGq;^R2pN_#PyItN#+iMb zt%7~KqF2Zx?ajMuOX-n>uYTp?(9~Pc;+R5W<4`w`&+VPB<|E`TGSzc-P%&Gf&`ZzX zfZy6}jTgKZEk%KUiG;hKq$Hek9}q8BA?s!s!V3;_(v!@3d^7Qj^je1_6&1(kT^J?n z(KQt;*(I|D6|d}FM!JsqwtF+7wJlvEC_d+`0l0`sBw)l&+*i4y&(XF2ag4}cTWNxh zxFQ|9c(TIrkTmY|B>@L%mh2Iyh7cJ8?Xr3b=1n)!^g@|w?wtn?;liDEMXS&g54SU2 zUX9FX=G2`IO7$iFb3FD*h{#3W@UJKB7%4&mbzCDM&hbv0bqyyT4FY1W>s+P_243%z zZu%_$)-dzT5+`dj0^T5j5Vgep`GzL z39Vko^3JSf+o!90r?|^y82WD8o-=ZrL6Qv9T)3Wev^?!WqQ1hB)}YyMVbx6xU&>)f z`9DSSKdJ^Q;nUv)w)3U;!0~1D?c@vQo@kEvZ1)~t=}>?8P1c|k7_mD4!+oxezt%g| zo6`EDH4l~Em9#@Bcl)*jRuX9xezY1Eh*}IA(AadgnoNC^&Kq0D-Iw=9Z(uxjCND31 zIPR?o)CQk$GVg8j`e8@pGImnnZ_E|vI-w~nR^f09Ik@f?2G9kw9(v<%e{C}+!~7Y@ z4Pia)mJy+AS)iDq$bn=&LSiq~1SMU5v*I@u=)Y-Ub95w;JaC^Y*B^XcUH+ww?#>j` zn8)H=*v6wQh#p1#Y$4O?#Av+ZL*3KLltS$@;pCAVX5rUJBVeXm{^9kKpqg;-a4u9h zV*W`Xygfg~=2)Xu=~uzRtU;Y1rB9E9@mbbfiCqjymDMu-oI`{^4a8`OIvB|-;|=;w z+_bf1(}L9j8z>6@O9%YfO!UV9F=EpKTW>?y)PR}LVGHj+rvD!S&fb*Ifo2y4QXyvu z+}i9)0NW?w6ILpgi9RVe!IhK(nrMZ+!H_Pk4)sI$g2VC@IL4x7P9`32)Ouv}P50<( z=>Ejt`duSRTED(u?eMkyxjm)ScxyyJN3ygpNZ{h^?KMqfRr;p4XZEv<8&_x{{aZSj zz2Gxm8pzFlo77_v+&_JCV&aF@+LS>}5PijoeUHc&b7#xiO>w5)GjqRsow$FP^OE)% zrX>4O?)ng84Vi{pqmdXh_e8t&X+6eT$A!hsPhC7e-#qiimZn{uHO+Z1sR_TGt9-W8 z#n<-NZ$O^uM-~Z`?bG14HkSg-Idx{7lyzkER8dqqhB*#BeoU$M@v)1o+Zat=DdlYY zo>odz&0D_K%3m2}Jwiv()1MLbH`5}{I6E2YjdZk4Km33?Pg*b=0GF+Q;~VES$IitF zfUQlo&gZ_lZGBpXJA${6u57}z9#gy2mg*!?z$g6y+Cyl3V(MGpq3FT97>ws(H~184 zUU%m-x?%KA#y$L~7XG53Y@Od*Ak9Xw8xdHr2X9wOtkICRRCeyHa2^ z*6pfPy+`LOW9Qq_x49F;P6_npUye9u_>nRz`r-!+f0DP-HbIJ9{iFZB9BTd6Gy8@$ ziNC)#o6oZpJ^aE8S2_)%HyuYVX!=I(YM`uqM12)x@F}}GYif+%LZ{)*5Uy1gtv69_ zkFly`>Sa)=ak3J`mrIRN*(enAF95Gk<$)%Cc!d7&4Fc0kFV;$YkyeUGryjLD+l2HR+;k%}C5cdJ z6Cv4i_4hQk;*p=lb%@aYU#WSK4H4RWaO>9ho*xckx3RNZI2z)1m0qX< z=bv6G=RYRsfzFp;@2U}yU|;pNL9sUg{#y|or~=L}R9Xe>{|V&|8u7p>X(*;K!>u?9 zU_8@-pa)`#N>M1wWwrahWn=4`8p{qV* zx2NY#skmTzioFRJQa!v)gkRznNNI}|yT&%K$g4&KP)<8h1Z6iPHtoIYR{_GH>P=Bs zC@qw1!M5j`bJ?tpeqEprwWK2oKp3XXo6f`^T4TA+Nod=eO}{RB)qS5oO6wlnGVCMe z0@)lX;S3xa*3PD?4or zMOB(2kAqsIXK@vq7|F#>w+yiu@z$b6#XK)KHE35Fo3c4IY3`iIw2EwK1rEH`3gp%< zq?CU3XLa041^}$PBEGS=4_3L8wZTiLU&9vOC%B_5J9IvyK^H9 z0a69t3-UM=l15rG7l3NLtcG@MoO4%ZjFl0iQVW?9*~z;A=QP+*!smD8S`x=Cs)i?U zG@&72vNw901~E5VdQ%M8IdKwPxHZtiL8{*e*Nl(nlEp=AcDr+vXfr zjiNKIOL0<50=P8n2Pxt`9W@I!mL>h_4E<}(XNjVas|*od*W?6ZtVRg0Jn?RlEv`)Q zkCFM~v8*dY?IFdSjTG`RPKq-lV-?P7HB^(uOwhNK#~lW1Uc*cCF_Bv{X$u~gbo+kn zE`LKvoz4!DiYX8jhL)0;1r$+0f`dgNrXZqx-qig(V7# zKw;CG&`$}V@d0w6>EL;Q6V(3zcl`FMUJJN}-sbGhDn<$}H?SGzp@+nG4R3F!vv4jK zbfvn+8>jj0PeNOFHLpB5YjffVpo%dKvO(rZ6OG4l82 z^y{4dHFob^w6pOXMW#o$mfRycHsGgm#&J~R@cxwz>gpfbaeb9v%v8w7T;q0sr8Qu? zET@NT#5bCMl!kD-;g!Dj8#vFrXi%*Wi(a?!pyqgFk8PDoYe+pU-?2RlfB=DuZ*|v;M@~A8a zVhc5ls=XXts}$(J))- zkLy=0Z|$sgJDWT0LOYbZK<3~8styX^;NWltQ?Fa5=I#I%`!_{Tbnhwg-l;C2R~d?t9KkWw=5bqVIvTv_2#qY z)Fiyy6P1c0Ms|v=EKS|bNo}pIg0I+`R3n#Bk}DQ(EYAK?l}zvrZQH4hm6K3cfOQXo ze>$ae<;{NA2myvRUFX-D8VItXx12}j7++`iw(&}q(@2}rCuQ{uSFYi^X(pLv%mfk$ zehT_xrg-irmI-ZS#0sOMU?;9cE>XGI%t&34oFf%qym8XBbnPzU`go&?HX=sa8yr=* zWsObjh@qot6cPR5(ATHnYP9cP=Ix-jXRL)8a^2C{8}}V#s*|=U5|5J zq!wzZ8GiOE1+W{G1B%c9Lk|Zh6}nc?o0Xu9wZj6m`=BDVW#~z(HqwS(R}Je`Zx_vw zC_O2T<`mgR%~`Z?!~i+HAyPsO$^GM2qfn`wf=?vXC_Kpj0C2+ye(9@;7&uT!6vuI0cMN8-F6~TjyVkdS zv_<+=E0%$QEZpOPK}nhR2!>p_;8y1N!UDZJRupTFMNG1?k%IQSt{`vPZ}bR>pY;6!S8K z>p+NZR5NF`D=PK&sEum;vWHT@@GB_FttSI>6Vj6b_Ksp8mEfA!a_Y+HxgF~_%n~t( z05*))g{{bu0{5v4i7H8Q0B#4RM&=m6jnw`XXl{8q;0i`T&fpKd05w7d1P&^l>IV6N z9Cj5vf#DmFZXT6N7egaFoDzCa1ThH#z$^u7M=GgU`;koxoD~%(lw9s31avg)11{~n z#mfERSXzdjH4QpwS$=Rbwa_C;9J!QbO;esk7bKwI(rW6$x#IUQG*=B77$D-W>Gq(o z$gOV`>B8Go^HgCa8^0QdPF!Hu1eKkQ;_i*x2o^OQRr~Niv9G`D-cS7m)kqtqSiU*Y zZ-49E{{Zw8P+vpI=%R{dAZaL~fdv#%OhHLYNk9b@Q9y!hdZ3JZCI@Yuq%eNMTSFKngITvNSCw-%7NO);*ws^Bnaxx@L|I zsF>W`WSZ!TvNOHQjJRy>z^BP^8XzQRCjzo=u6JAo5)GrS4NVl$%`)YHMD;YrYnCq% zD4_?bsuuD{*RQz_!N5MX4c?$r7`qssRn_qKxe{k;@p1D;W(?Tu5$p2_zqPJ?hhijvVBE zbwei8VR_7wxbJ~iW`N-c>z++l)9;oDf?zRaCxccZxM^d{U=n&}ngU{sCXDdd9V$R( zIVu1-=~4NrqjpAnoYadV707UV^`DPXrRQje=q=7 zJ2Zb~)EzrFBDW-(Cm@NU{{X(lW@-W=!f!S`!~L;bwRJ~LJsB2oY1^v5of-cCma+c; z-$H>rWByvl{{Vdo<~lA*B;;&DA3PN)% zU-#Ip5hSt07){;jhCn%9-Kw%`5NsKiK7ZR3$uA*8kt}2%_Qg;Rd4G6u$Ed39d2h6W zdCn>+?jmEgCX5z5o#L9Z+C_-Wkumk&DF~U!@>s8N#ZitU2I(A(_Nx-kgat{W8UFxm zR5JNqWJ>eKLF@C1Oa#+|OLG4JN>E-IPSzs^w1BjmQDmmx05IOTA+<)5@ zAWpN98XsC7=W4eZK9xe+>M0@GWsf8Mp;P&>9&qs~eFPj)AzVS>AVRSyyks!$LQ$;&8?m5~zR1uqa6cIFAy?2Ut zn+YI^q95=vKo>;u;G>cF)>OMgoxJl*yYkt$3FALU6&ITj0C}8uTU9%DhGl9_-}MHB#0MHC1PB?gLM6j4P2 K3Mit0KmXas`-5%( literal 0 HcmV?d00001 diff --git a/tensorboardX/__init__.py b/tensorboardX/__init__.py new file mode 100644 index 0000000..e910378 --- /dev/null +++ b/tensorboardX/__init__.py @@ -0,0 +1,5 @@ +"""A module for visualization with tensorboard +""" + +from .writer import FileWriter, SummaryWriter +from .record_writer import RecordWriter diff --git a/tensorboardX/crc32c.py b/tensorboardX/crc32c.py new file mode 100644 index 0000000..62c9e0c --- /dev/null +++ b/tensorboardX/crc32c.py @@ -0,0 +1,123 @@ +import array + + +CRC_TABLE = ( + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, + 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, + 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, + 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, + 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, + 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, + 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, + 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, + 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, + 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, + 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, + 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, + 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, + 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, + 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, + 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, + 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, + 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, + 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, + 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, + 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, + 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, + 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, + 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, + 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, + 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, + 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, + 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, + 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, + 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, + 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, + 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, + 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351, +) + + +CRC_INIT = 0 + +_MASK = 0xFFFFFFFF + + +def crc_update(crc, data): + """Update CRC-32C checksum with data. + + Args: + crc: 32-bit checksum to update as long. + data: byte array, string or iterable over bytes. + + Returns: + 32-bit updated CRC-32C as long. + """ + + if type(data) != array.array or data.itemsize != 1: + buf = array.array("B", data) + else: + buf = data + + crc ^= _MASK + for b in buf: + table_index = (crc ^ b) & 0xff + crc = (CRC_TABLE[table_index] ^ (crc >> 8)) & _MASK + return crc ^ _MASK + + +def crc_finalize(crc): + """Finalize CRC-32C checksum. + + This function should be called as last step of crc calculation. + + Args: + crc: 32-bit checksum as long. + + Returns: + finalized 32-bit checksum as long + """ + return crc & _MASK + + +def crc32c(data): + """Compute CRC-32C checksum of the data. + + Args: + data: byte array, string or iterable over bytes. + + Returns: + 32-bit CRC-32C checksum of data as long. + """ + return crc_finalize(crc_update(CRC_INIT, data)) diff --git a/tensorboardX/embedding.py b/tensorboardX/embedding.py new file mode 100644 index 0000000..4e1935f --- /dev/null +++ b/tensorboardX/embedding.py @@ -0,0 +1,49 @@ +import os + + +def make_tsv(metadata, save_path): + metadata = [str(x) for x in metadata] + with open(os.path.join(save_path, 'metadata.tsv'), 'w') as f: + for x in metadata: + f.write(x + '\n') + + +# https://github.com/tensorflow/tensorboard/issues/44 image label will be squared +def make_sprite(label_img, save_path): + import math + import torch + import torchvision + from .x2num import makenp + # this ensures the sprite image has correct dimension as described in + # https://www.tensorflow.org/get_started/embedding_viz + nrow = int(math.ceil((label_img.size(0)) ** 0.5)) + + label_img = torch.from_numpy(makenp(label_img)) # for other framework + # augment images so that #images equals nrow*nrow + label_img = torch.cat((label_img, torch.randn(nrow ** 2 - label_img.size(0), *label_img.size()[1:]) * 255), 0) + + torchvision.utils.save_image(label_img, os.path.join(save_path, 'sprite.png'), nrow=nrow, padding=0) + + +def append_pbtxt(metadata, label_img, save_path, global_step, tag): + with open(os.path.join(save_path, 'projector_config.pbtxt'), 'a') as f: + # step = os.path.split(save_path)[-1] + f.write('embeddings {\n') + f.write('tensor_name: "{}:{}"\n'.format(tag, global_step)) + f.write('tensor_path: "{}"\n'.format(os.path.join(global_step, 'tensors.tsv'))) + if metadata is not None: + f.write('metadata_path: "{}"\n'.format(os.path.join(global_step, 'metadata.tsv'))) + if label_img is not None: + f.write('sprite {\n') + f.write('image_path: "{}"\n'.format(os.path.join(global_step, 'sprite.png'))) + f.write('single_image_dim: {}\n'.format(label_img.size(3))) + f.write('single_image_dim: {}\n'.format(label_img.size(2))) + f.write('}\n') + f.write('}\n') + + +def make_mat(matlist, save_path): + with open(os.path.join(save_path, 'tensors.tsv'), 'w') as f: + for x in matlist: + x = [str(i) for i in x] + f.write('\t'.join(x) + '\n') diff --git a/tensorboardX/event_file_writer.py b/tensorboardX/event_file_writer.py new file mode 100644 index 0000000..0f99838 --- /dev/null +++ b/tensorboardX/event_file_writer.py @@ -0,0 +1,196 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Writes events to disk in a logdir.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import logging +import os.path +import socket +import threading +import time + +import six + +from .src import event_pb2 +from .record_writer import RecordWriter + + +def directory_check(path): + '''Initialize the directory for log files.''' + # If the direcotry does not exist, create it! + if not os.path.exists(path): + os.makedirs(path) + + +class EventsWriter(object): + '''Writes `Event` protocol buffers to an event file.''' + + def __init__(self, file_prefix): + ''' + Events files have a name of the form + '/some/file/path/events.out.tfevents.[timestamp].[hostname]' + ''' + self._file_prefix = file_prefix + ".out.tfevents." + str(time.time())[:10] + "." + socket.gethostname() + + # Open(Create) the log file with the particular form of name. + logging.basicConfig(filename=self._file_prefix) + + self._num_outstanding_events = 0 + + self._py_recordio_writer = RecordWriter(self._file_prefix) + + # Initialize an event instance. + self._event = event_pb2.Event() + + self._event.wall_time = time.time() + + self.write_event(self._event) + + def write_event(self, event): + '''Append "event" to the file.''' + + # Check if event is of type event_pb2.Event proto. + if not isinstance(event, event_pb2.Event): + raise TypeError("Expected an event_pb2.Event proto, " + " but got %s" % type(event)) + return self._write_serialized_event(event.SerializeToString()) + + def _write_serialized_event(self, event_str): + self._num_outstanding_events += 1 + self._py_recordio_writer.write(event_str) + + def flush(self): + '''Flushes the event file to disk.''' + self._num_outstanding_events = 0 + return True + + def close(self): + '''Call self.flush().''' + return_value = self.flush() + return return_value + + +class EventFileWriter(object): + """Writes `Event` protocol buffers to an event file. + The `EventFileWriter` class creates an event file in the specified directory, + and asynchronously writes Event protocol buffers to the file. The Event file + is encoded using the tfrecord format, which is similar to RecordIO. + @@__init__ + @@add_event + @@flush + @@close + """ + + def __init__(self, logdir, max_queue=10, flush_secs=120): + """Creates a `EventFileWriter` and an event file to write to. + On construction the summary writer creates a new event file in `logdir`. + This event file will contain `Event` protocol buffers, which are written to + disk via the add_event method. + The other arguments to the constructor control the asynchronous writes to + the event file: + * `flush_secs`: How often, in seconds, to flush the added summaries + and events to disk. + * `max_queue`: Maximum number of summaries or events pending to be + written to disk before one of the 'add' calls block. + Args: + logdir: A string. Directory where event file will be written. + max_queue: Integer. Size of the queue for pending events and summaries. + flush_secs: Number. How often, in seconds, to flush the + pending events and summaries to disk. + """ + self._logdir = logdir + directory_check(self._logdir) + self._event_queue = six.moves.queue.Queue(max_queue) + self._ev_writer = EventsWriter(os.path.join(self._logdir, "events")) + self._closed = False + self._worker = _EventLoggerThread(self._event_queue, self._ev_writer, + flush_secs) + + self._worker.start() + + def get_logdir(self): + """Returns the directory where event file will be written.""" + return self._logdir + + def reopen(self): + """Reopens the EventFileWriter. + Can be called after `close()` to add more events in the same directory. + The events will go into a new events file. + Does nothing if the EventFileWriter was not closed. + """ + if self._closed: + self._closed = False + + def add_event(self, event): + """Adds an event to the event file. + Args: + event: An `Event` protocol buffer. + """ + if not self._closed: + self._event_queue.put(event) + + def flush(self): + """Flushes the event file to disk. + Call this method to make sure that all pending events have been written to + disk. + """ + self._event_queue.join() + self._ev_writer.flush() + + def close(self): + """Flushes the event file to disk and close the file. + Call this method when you do not need the summary writer anymore. + """ + self.flush() + self._ev_writer.close() + self._closed = True + + +class _EventLoggerThread(threading.Thread): + """Thread that logs events.""" + + def __init__(self, queue, ev_writer, flush_secs): + """Creates an _EventLoggerThread. + Args: + queue: A Queue from which to dequeue events. + ev_writer: An event writer. Used to log brain events for + the visualizer. + flush_secs: How often, in seconds, to flush the + pending file to disk. + """ + threading.Thread.__init__(self) + self.daemon = True + self._queue = queue + self._ev_writer = ev_writer + self._flush_secs = flush_secs + # The first event will be flushed immediately. + self._next_event_flush_time = 0 + + def run(self): + while True: + event = self._queue.get() + try: + self._ev_writer.write_event(event) + # Flush the event writer every so often. + now = time.time() + if now > self._next_event_flush_time: + self._ev_writer.flush() + # Do it again in two minutes. + self._next_event_flush_time = now + self._flush_secs + finally: + self._queue.task_done() diff --git a/tensorboardX/graph.py b/tensorboardX/graph.py new file mode 100644 index 0000000..8135fd3 --- /dev/null +++ b/tensorboardX/graph.py @@ -0,0 +1,66 @@ +from .src.graph_pb2 import GraphDef +from .src.node_def_pb2 import NodeDef +from .src.versions_pb2 import VersionDef +from .src.attr_value_pb2 import AttrValue +from .src.tensor_shape_pb2 import TensorShapeProto + +from distutils.version import LooseVersion + + +def replace(name, scope): + return '/'.join([scope[name], name]) + + +def parse(graph): + import torch + scope = {} + for n in graph.nodes(): + inputs = [i.uniqueName() for i in n.inputs()] + for i in range(1, len(inputs)): + scope[inputs[i]] = n.scopeName() + + uname = next(iter(n.outputs())).uniqueName() + assert n.scopeName() != '', '{} has empty scope name'.format(n) + scope[uname] = n.scopeName() + if LooseVersion(torch.__version__) >= LooseVersion("0.4"): + scope['0'] = 'input' + else: + scope['1'] = 'input' + + nodes = [] + for n in graph.nodes(): + attrs = {k: n[k] for k in n.attributeNames()} + attrs = str(attrs).replace("'", ' ') # singlequote will be escaped by tensorboard + if any(i.uniqueName() not in scope.keys() for i in n.inputs()): # 0.3.1 workaround + continue + inputs = [replace(i.uniqueName(), scope) for i in n.inputs()] + uname = next(iter(n.outputs())).uniqueName() # FIXME: only first output is considered + nodes.append({'name': replace(uname, scope), 'op': n.kind(), 'inputs': inputs, 'attr': attrs}) + + for n in graph.inputs(): + uname = n.uniqueName() + if uname not in scope.keys(): + scope[uname] = 'unused' + nodes.append({'name': replace(uname, scope), 'op': 'Parameter', 'inputs': [], 'attr': str(n.type())}) + + return nodes + + +def graph(model, args, verbose=False): + import torch + with torch.onnx.set_training(model, False): + trace, _ = torch.jit.trace(model, args) + if LooseVersion(torch.__version__) >= LooseVersion("0.4"): + torch.onnx._optimize_trace(trace, False) + else: + torch.onnx._optimize_trace(trace) + graph = trace.graph() + if verbose: + print(graph) + list_of_nodes = parse(graph) + nodes = [] + for node in list_of_nodes: + nodes.append( + NodeDef(name=node['name'], op=node['op'], input=node['inputs'], + attr={'lanpa': AttrValue(s=node['attr'].encode(encoding='utf_8'))})) + return GraphDef(node=nodes, versions=VersionDef(producer=22)) diff --git a/tensorboardX/graph_onnx.py b/tensorboardX/graph_onnx.py new file mode 100644 index 0000000..96a3534 --- /dev/null +++ b/tensorboardX/graph_onnx.py @@ -0,0 +1,158 @@ +from .src.graph_pb2 import GraphDef +from .src.node_def_pb2 import NodeDef +from .src.versions_pb2 import VersionDef +from .src.attr_value_pb2 import AttrValue +from .src.tensor_shape_pb2 import TensorShapeProto +# from .src.onnx_pb2 import ModelProto + + +def gg(fname): + import onnx # 0.2.1 + m = onnx.load(fname) + nodes_proto = [] + nodes = [] + g = m.graph + import itertools + for node in itertools.chain(g.input, g.output): + nodes_proto.append(node) + + for node in nodes_proto: + shapeproto = TensorShapeProto( + dim=[TensorShapeProto.Dim(size=d.dim_value) for d in node.type.tensor_type.shape.dim]) + nodes.append(NodeDef( + name=node.name, + op='Variable', + input=[], + attr={ + 'dtype': AttrValue(type=node.type.tensor_type.elem_type), + 'shape': AttrValue(shape=shapeproto), + }) + ) + + for node in g.node: + attr = [] + for s in node.attribute: + attr.append(' = '.join([str(f[1]) for f in s.ListFields()])) + attr = ', '.join(attr).encode(encoding='utf_8') + + nodes.append(NodeDef( + name=node.output[0], + op=node.op_type, + input=node.input, + attr={'parameters': AttrValue(s=attr)}, + )) + # two pass token replacement, appends opname to object id + mapping = {} + for node in nodes: + mapping[node.name] = node.op + '_' + node.name + + nodes, mapping = updatenodes(nodes, mapping) + mapping = smartGrouping(nodes, mapping) + nodes, mapping = updatenodes(nodes, mapping) + + return GraphDef(node=nodes, versions=VersionDef(producer=22)) + + +def updatenodes(nodes, mapping): + for node in nodes: + newname = mapping[node.name] + node.name = newname + newinput = [] + for inputnode in list(node.input): + newinput.append(mapping[inputnode]) + node.input.remove(inputnode) + node.input.extend(newinput) + newmap = {} + for k, v in mapping.items(): + newmap[v] = v + return nodes, newmap + + +def findnode(nodes, name): + """ input: node name + returns: node object + """ + for n in nodes: + if n.name == name: + return n + + +def parser(s, nodes, node): + print(s) + if len(s) == 0: + return + if len(s) > 0: + if s[0] == node.op: + print(s[0], node.name, s[1], node.input) + for n in node.input: + print(n, s[1]) + parser(s[1], nodes, findnode(nodes, n)) + else: + return False + + +# TODO: use recursive parse + +def smartGrouping(nodes, mapping): + # a Fully Conv is: (TODO: check var1.size(0)==var2.size(0)) + # GEMM <-- Variable (c1) + # ^-- Transpose (c2) <-- Variable (c3) + + # a Conv with bias is: (TODO: check var1.size(0)==var2.size(0)) + # Add <-- Conv (c2) <-- Variable (c3) + # ^-- Variable (c1) + # + # gemm = ('Gemm', ('Variable', ('Transpose', ('Variable')))) + + FCcounter = 1 + Convcounter = 1 + for node in nodes: + if node.op == 'Gemm': + c1 = c2 = c3 = False + for name_in in node.input: + n = findnode(nodes, name_in) + if n.op == 'Variable': + c1 = True + c1name = n.name + if n.op == 'Transpose': + c2 = True + c2name = n.name + if len(n.input) == 1: + nn = findnode(nodes, n.input[0]) + if nn.op == 'Variable': + c3 = True + c3name = nn.name + # print(n.op, n.name, c1, c2, c3) + if c1 and c2 and c3: + # print(c1name, c2name, c3name) + mapping[c1name] = 'FC{}/{}'.format(FCcounter, c1name) + mapping[c2name] = 'FC{}/{}'.format(FCcounter, c2name) + mapping[c3name] = 'FC{}/{}'.format(FCcounter, c3name) + mapping[node.name] = 'FC{}/{}'.format(FCcounter, node.name) + FCcounter += 1 + continue + if node.op == 'Add': + c1 = c2 = c3 = False + for name_in in node.input: + n = findnode(nodes, name_in) + if n.op == 'Variable': + c1 = True + c1name = n.name + if n.op == 'Conv': + c2 = True + c2name = n.name + if len(n.input) >= 1: + for nn_name in n.input: + nn = findnode(nodes, nn_name) + if nn.op == 'Variable': + c3 = True + c3name = nn.name + + if c1 and c2 and c3: + # print(c1name, c2name, c3name) + mapping[c1name] = 'Conv{}/{}'.format(Convcounter, c1name) + mapping[c2name] = 'Conv{}/{}'.format(Convcounter, c2name) + mapping[c3name] = 'Conv{}/{}'.format(Convcounter, c3name) + mapping[node.name] = 'Conv{}/{}'.format(Convcounter, node.name) + Convcounter += 1 + return mapping diff --git a/tensorboardX/record_writer.py b/tensorboardX/record_writer.py new file mode 100644 index 0000000..c14bd7b --- /dev/null +++ b/tensorboardX/record_writer.py @@ -0,0 +1,47 @@ +""" +To write tf_record into file. Here we use it for tensorboard's event writting. +The code was borrow from https://github.com/TeamHG-Memex/tensorboard_logger +""" + +import re +import struct + +from .crc32c import crc32c + +_VALID_OP_NAME_START = re.compile('^[A-Za-z0-9.]') +_VALID_OP_NAME_PART = re.compile('[A-Za-z0-9_.\\-/]+') + + +class RecordWriter(object): + def __init__(self, path, flush_secs=2): + self._name_to_tf_name = {} + self._tf_names = set() + self.path = path + self.flush_secs = flush_secs # TODO. flush every flush_secs, not every time. + self._writer = None + self._writer = open(path, 'wb') + + def write(self, event_str): + w = self._writer.write + header = struct.pack('Q', len(event_str)) + w(header) + w(struct.pack('I', masked_crc32c(header))) + w(event_str) + w(struct.pack('I', masked_crc32c(event_str))) + self._writer.flush() + + +def masked_crc32c(data): + x = u32(crc32c(data)) + return u32(((x >> 15) | u32(x << 17)) + 0xa282ead8) + + +def u32(x): + return x & 0xffffffff + + +def make_valid_tf_name(name): + if not _VALID_OP_NAME_START.match(name): + # Must make it valid somehow, but don't want to remove stuff + name = '.' + name + return '_'.join(_VALID_OP_NAME_PART.findall(name)) diff --git a/tensorboardX/src/__init__.py b/tensorboardX/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tensorboardX/src/attr_value_pb2.py b/tensorboardX/src/attr_value_pb2.py new file mode 100644 index 0000000..95fae04 --- /dev/null +++ b/tensorboardX/src/attr_value_pb2.py @@ -0,0 +1,372 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/attr_value.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import tensor_pb2 as tensorboardX_dot_src_dot_tensor__pb2 +from tensorboardX.src import tensor_shape_pb2 as tensorboardX_dot_src_dot_tensor__shape__pb2 +from tensorboardX.src import types_pb2 as tensorboardX_dot_src_dot_types__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/attr_value.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n!tensorboardX/src/attr_value.proto\x12\x0btensorboard\x1a\x1dtensorboardX/src/tensor.proto\x1a#tensorboardX/src/tensor_shape.proto\x1a\x1ctensorboardX/src/types.proto\"\xaf\x04\n\tAttrValue\x12\x0b\n\x01s\x18\x02 \x01(\x0cH\x00\x12\x0b\n\x01i\x18\x03 \x01(\x03H\x00\x12\x0b\n\x01\x66\x18\x04 \x01(\x02H\x00\x12\x0b\n\x01\x62\x18\x05 \x01(\x08H\x00\x12%\n\x04type\x18\x06 \x01(\x0e\x32\x15.tensorboard.DataTypeH\x00\x12.\n\x05shape\x18\x07 \x01(\x0b\x32\x1d.tensorboard.TensorShapeProtoH\x00\x12*\n\x06tensor\x18\x08 \x01(\x0b\x32\x18.tensorboard.TensorProtoH\x00\x12\x30\n\x04list\x18\x01 \x01(\x0b\x32 .tensorboard.AttrValue.ListValueH\x00\x12)\n\x04\x66unc\x18\n \x01(\x0b\x32\x19.tensorboard.NameAttrListH\x00\x12\x15\n\x0bplaceholder\x18\t \x01(\tH\x00\x1a\xed\x01\n\tListValue\x12\t\n\x01s\x18\x02 \x03(\x0c\x12\r\n\x01i\x18\x03 \x03(\x03\x42\x02\x10\x01\x12\r\n\x01\x66\x18\x04 \x03(\x02\x42\x02\x10\x01\x12\r\n\x01\x62\x18\x05 \x03(\x08\x42\x02\x10\x01\x12\'\n\x04type\x18\x06 \x03(\x0e\x32\x15.tensorboard.DataTypeB\x02\x10\x01\x12,\n\x05shape\x18\x07 \x03(\x0b\x32\x1d.tensorboard.TensorShapeProto\x12(\n\x06tensor\x18\x08 \x03(\x0b\x32\x18.tensorboard.TensorProto\x12\'\n\x04\x66unc\x18\t \x03(\x0b\x32\x19.tensorboard.NameAttrListB\x07\n\x05value\"\x94\x01\n\x0cNameAttrList\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x31\n\x04\x61ttr\x18\x02 \x03(\x0b\x32#.tensorboard.NameAttrList.AttrEntry\x1a\x43\n\tAttrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.tensorboard.AttrValue:\x02\x38\x01\x42\x30\n\x18org.tensorflow.frameworkB\x0f\x41ttrValueProtosP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_tensor__pb2.DESCRIPTOR,tensorboardX_dot_src_dot_tensor__shape__pb2.DESCRIPTOR,tensorboardX_dot_src_dot_types__pb2.DESCRIPTOR,]) + + + + +_ATTRVALUE_LISTVALUE = _descriptor.Descriptor( + name='ListValue', + full_name='tensorboard.AttrValue.ListValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='s', full_name='tensorboard.AttrValue.ListValue.s', index=0, + number=2, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='i', full_name='tensorboard.AttrValue.ListValue.i', index=1, + number=3, type=3, cpp_type=2, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='f', full_name='tensorboard.AttrValue.ListValue.f', index=2, + number=4, type=2, cpp_type=6, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='b', full_name='tensorboard.AttrValue.ListValue.b', index=3, + number=5, type=8, cpp_type=7, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='type', full_name='tensorboard.AttrValue.ListValue.type', index=4, + number=6, type=14, cpp_type=8, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='shape', full_name='tensorboard.AttrValue.ListValue.shape', index=5, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tensor', full_name='tensorboard.AttrValue.ListValue.tensor', index=6, + number=8, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='func', full_name='tensorboard.AttrValue.ListValue.func', index=7, + number=9, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=462, + serialized_end=699, +) + +_ATTRVALUE = _descriptor.Descriptor( + name='AttrValue', + full_name='tensorboard.AttrValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='s', full_name='tensorboard.AttrValue.s', index=0, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='i', full_name='tensorboard.AttrValue.i', index=1, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='f', full_name='tensorboard.AttrValue.f', index=2, + number=4, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='b', full_name='tensorboard.AttrValue.b', index=3, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='type', full_name='tensorboard.AttrValue.type', index=4, + number=6, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='shape', full_name='tensorboard.AttrValue.shape', index=5, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tensor', full_name='tensorboard.AttrValue.tensor', index=6, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='list', full_name='tensorboard.AttrValue.list', index=7, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='func', full_name='tensorboard.AttrValue.func', index=8, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='placeholder', full_name='tensorboard.AttrValue.placeholder', index=9, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_ATTRVALUE_LISTVALUE, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='tensorboard.AttrValue.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=149, + serialized_end=708, +) + + +_NAMEATTRLIST_ATTRENTRY = _descriptor.Descriptor( + name='AttrEntry', + full_name='tensorboard.NameAttrList.AttrEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='tensorboard.NameAttrList.AttrEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='tensorboard.NameAttrList.AttrEntry.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=792, + serialized_end=859, +) + +_NAMEATTRLIST = _descriptor.Descriptor( + name='NameAttrList', + full_name='tensorboard.NameAttrList', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='tensorboard.NameAttrList.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='attr', full_name='tensorboard.NameAttrList.attr', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_NAMEATTRLIST_ATTRENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=711, + serialized_end=859, +) + +_ATTRVALUE_LISTVALUE.fields_by_name['type'].enum_type = tensorboardX_dot_src_dot_types__pb2._DATATYPE +_ATTRVALUE_LISTVALUE.fields_by_name['shape'].message_type = tensorboardX_dot_src_dot_tensor__shape__pb2._TENSORSHAPEPROTO +_ATTRVALUE_LISTVALUE.fields_by_name['tensor'].message_type = tensorboardX_dot_src_dot_tensor__pb2._TENSORPROTO +_ATTRVALUE_LISTVALUE.fields_by_name['func'].message_type = _NAMEATTRLIST +_ATTRVALUE_LISTVALUE.containing_type = _ATTRVALUE +_ATTRVALUE.fields_by_name['type'].enum_type = tensorboardX_dot_src_dot_types__pb2._DATATYPE +_ATTRVALUE.fields_by_name['shape'].message_type = tensorboardX_dot_src_dot_tensor__shape__pb2._TENSORSHAPEPROTO +_ATTRVALUE.fields_by_name['tensor'].message_type = tensorboardX_dot_src_dot_tensor__pb2._TENSORPROTO +_ATTRVALUE.fields_by_name['list'].message_type = _ATTRVALUE_LISTVALUE +_ATTRVALUE.fields_by_name['func'].message_type = _NAMEATTRLIST +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['s']) +_ATTRVALUE.fields_by_name['s'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['i']) +_ATTRVALUE.fields_by_name['i'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['f']) +_ATTRVALUE.fields_by_name['f'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['b']) +_ATTRVALUE.fields_by_name['b'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['type']) +_ATTRVALUE.fields_by_name['type'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['shape']) +_ATTRVALUE.fields_by_name['shape'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['tensor']) +_ATTRVALUE.fields_by_name['tensor'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['list']) +_ATTRVALUE.fields_by_name['list'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['func']) +_ATTRVALUE.fields_by_name['func'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_ATTRVALUE.oneofs_by_name['value'].fields.append( + _ATTRVALUE.fields_by_name['placeholder']) +_ATTRVALUE.fields_by_name['placeholder'].containing_oneof = _ATTRVALUE.oneofs_by_name['value'] +_NAMEATTRLIST_ATTRENTRY.fields_by_name['value'].message_type = _ATTRVALUE +_NAMEATTRLIST_ATTRENTRY.containing_type = _NAMEATTRLIST +_NAMEATTRLIST.fields_by_name['attr'].message_type = _NAMEATTRLIST_ATTRENTRY +DESCRIPTOR.message_types_by_name['AttrValue'] = _ATTRVALUE +DESCRIPTOR.message_types_by_name['NameAttrList'] = _NAMEATTRLIST +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +AttrValue = _reflection.GeneratedProtocolMessageType('AttrValue', (_message.Message,), dict( + + ListValue = _reflection.GeneratedProtocolMessageType('ListValue', (_message.Message,), dict( + DESCRIPTOR = _ATTRVALUE_LISTVALUE, + __module__ = 'tensorboardX.src.attr_value_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.AttrValue.ListValue) + )) + , + DESCRIPTOR = _ATTRVALUE, + __module__ = 'tensorboardX.src.attr_value_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.AttrValue) + )) +_sym_db.RegisterMessage(AttrValue) +_sym_db.RegisterMessage(AttrValue.ListValue) + +NameAttrList = _reflection.GeneratedProtocolMessageType('NameAttrList', (_message.Message,), dict( + + AttrEntry = _reflection.GeneratedProtocolMessageType('AttrEntry', (_message.Message,), dict( + DESCRIPTOR = _NAMEATTRLIST_ATTRENTRY, + __module__ = 'tensorboardX.src.attr_value_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.NameAttrList.AttrEntry) + )) + , + DESCRIPTOR = _NAMEATTRLIST, + __module__ = 'tensorboardX.src.attr_value_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.NameAttrList) + )) +_sym_db.RegisterMessage(NameAttrList) +_sym_db.RegisterMessage(NameAttrList.AttrEntry) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\017AttrValueProtosP\001\370\001\001')) +_ATTRVALUE_LISTVALUE.fields_by_name['i'].has_options = True +_ATTRVALUE_LISTVALUE.fields_by_name['i']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_ATTRVALUE_LISTVALUE.fields_by_name['f'].has_options = True +_ATTRVALUE_LISTVALUE.fields_by_name['f']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_ATTRVALUE_LISTVALUE.fields_by_name['b'].has_options = True +_ATTRVALUE_LISTVALUE.fields_by_name['b']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_ATTRVALUE_LISTVALUE.fields_by_name['type'].has_options = True +_ATTRVALUE_LISTVALUE.fields_by_name['type']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_NAMEATTRLIST_ATTRENTRY.has_options = True +_NAMEATTRLIST_ATTRENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/event_pb2.py b/tensorboardX/src/event_pb2.py new file mode 100644 index 0000000..459dfbd --- /dev/null +++ b/tensorboardX/src/event_pb2.py @@ -0,0 +1,376 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/event.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import summary_pb2 as tensorboardX_dot_src_dot_summary__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/event.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1ctensorboardX/src/event.proto\x12\x0btensorboard\x1a\x1etensorboardX/src/summary.proto\"\xbf\x02\n\x05\x45vent\x12\x11\n\twall_time\x18\x01 \x01(\x01\x12\x0c\n\x04step\x18\x02 \x01(\x03\x12\x16\n\x0c\x66ile_version\x18\x03 \x01(\tH\x00\x12\x13\n\tgraph_def\x18\x04 \x01(\x0cH\x00\x12\'\n\x07summary\x18\x05 \x01(\x0b\x32\x14.tensorboard.SummaryH\x00\x12.\n\x0blog_message\x18\x06 \x01(\x0b\x32\x17.tensorboard.LogMessageH\x00\x12.\n\x0bsession_log\x18\x07 \x01(\x0b\x32\x17.tensorboard.SessionLogH\x00\x12=\n\x13tagged_run_metadata\x18\x08 \x01(\x0b\x32\x1e.tensorboard.TaggedRunMetadataH\x00\x12\x18\n\x0emeta_graph_def\x18\t \x01(\x0cH\x00\x42\x06\n\x04what\"\x96\x01\n\nLogMessage\x12,\n\x05level\x18\x01 \x01(\x0e\x32\x1d.tensorboard.LogMessage.Level\x12\x0f\n\x07message\x18\x02 \x01(\t\"I\n\x05Level\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\x08\n\x04INFO\x10\x14\x12\x08\n\x04WARN\x10\x1e\x12\t\n\x05\x45RROR\x10(\x12\t\n\x05\x46\x41TAL\x10\x32\"\xb7\x01\n\nSessionLog\x12\x35\n\x06status\x18\x01 \x01(\x0e\x32%.tensorboard.SessionLog.SessionStatus\x12\x17\n\x0f\x63heckpoint_path\x18\x02 \x01(\t\x12\x0b\n\x03msg\x18\x03 \x01(\t\"L\n\rSessionStatus\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\t\n\x05START\x10\x01\x12\x08\n\x04STOP\x10\x02\x12\x0e\n\nCHECKPOINT\x10\x03\"6\n\x11TaggedRunMetadata\x12\x0b\n\x03tag\x18\x01 \x01(\t\x12\x14\n\x0crun_metadata\x18\x02 \x01(\x0c\x42\'\n\x13org.tensorflow.utilB\x0b\x45ventProtosP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_summary__pb2.DESCRIPTOR,]) + + + +_LOGMESSAGE_LEVEL = _descriptor.EnumDescriptor( + name='Level', + full_name='tensorboard.LogMessage.Level', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UNKNOWN', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DEBUG', index=1, number=10, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='INFO', index=2, number=20, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WARN', index=3, number=30, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ERROR', index=4, number=40, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FATAL', index=5, number=50, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=477, + serialized_end=550, +) +_sym_db.RegisterEnumDescriptor(_LOGMESSAGE_LEVEL) + +_SESSIONLOG_SESSIONSTATUS = _descriptor.EnumDescriptor( + name='SessionStatus', + full_name='tensorboard.SessionLog.SessionStatus', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='STATUS_UNSPECIFIED', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='START', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STOP', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CHECKPOINT', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=660, + serialized_end=736, +) +_sym_db.RegisterEnumDescriptor(_SESSIONLOG_SESSIONSTATUS) + + +_EVENT = _descriptor.Descriptor( + name='Event', + full_name='tensorboard.Event', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='wall_time', full_name='tensorboard.Event.wall_time', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='step', full_name='tensorboard.Event.step', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='file_version', full_name='tensorboard.Event.file_version', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='graph_def', full_name='tensorboard.Event.graph_def', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='summary', full_name='tensorboard.Event.summary', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='log_message', full_name='tensorboard.Event.log_message', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='session_log', full_name='tensorboard.Event.session_log', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tagged_run_metadata', full_name='tensorboard.Event.tagged_run_metadata', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='meta_graph_def', full_name='tensorboard.Event.meta_graph_def', index=8, + number=9, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='what', full_name='tensorboard.Event.what', + index=0, containing_type=None, fields=[]), + ], + serialized_start=78, + serialized_end=397, +) + + +_LOGMESSAGE = _descriptor.Descriptor( + name='LogMessage', + full_name='tensorboard.LogMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='level', full_name='tensorboard.LogMessage.level', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='message', full_name='tensorboard.LogMessage.message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _LOGMESSAGE_LEVEL, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=400, + serialized_end=550, +) + + +_SESSIONLOG = _descriptor.Descriptor( + name='SessionLog', + full_name='tensorboard.SessionLog', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='tensorboard.SessionLog.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='checkpoint_path', full_name='tensorboard.SessionLog.checkpoint_path', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='msg', full_name='tensorboard.SessionLog.msg', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _SESSIONLOG_SESSIONSTATUS, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=553, + serialized_end=736, +) + + +_TAGGEDRUNMETADATA = _descriptor.Descriptor( + name='TaggedRunMetadata', + full_name='tensorboard.TaggedRunMetadata', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='tag', full_name='tensorboard.TaggedRunMetadata.tag', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='run_metadata', full_name='tensorboard.TaggedRunMetadata.run_metadata', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=738, + serialized_end=792, +) + +_EVENT.fields_by_name['summary'].message_type = tensorboardX_dot_src_dot_summary__pb2._SUMMARY +_EVENT.fields_by_name['log_message'].message_type = _LOGMESSAGE +_EVENT.fields_by_name['session_log'].message_type = _SESSIONLOG +_EVENT.fields_by_name['tagged_run_metadata'].message_type = _TAGGEDRUNMETADATA +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['file_version']) +_EVENT.fields_by_name['file_version'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['graph_def']) +_EVENT.fields_by_name['graph_def'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['summary']) +_EVENT.fields_by_name['summary'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['log_message']) +_EVENT.fields_by_name['log_message'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['session_log']) +_EVENT.fields_by_name['session_log'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['tagged_run_metadata']) +_EVENT.fields_by_name['tagged_run_metadata'].containing_oneof = _EVENT.oneofs_by_name['what'] +_EVENT.oneofs_by_name['what'].fields.append( + _EVENT.fields_by_name['meta_graph_def']) +_EVENT.fields_by_name['meta_graph_def'].containing_oneof = _EVENT.oneofs_by_name['what'] +_LOGMESSAGE.fields_by_name['level'].enum_type = _LOGMESSAGE_LEVEL +_LOGMESSAGE_LEVEL.containing_type = _LOGMESSAGE +_SESSIONLOG.fields_by_name['status'].enum_type = _SESSIONLOG_SESSIONSTATUS +_SESSIONLOG_SESSIONSTATUS.containing_type = _SESSIONLOG +DESCRIPTOR.message_types_by_name['Event'] = _EVENT +DESCRIPTOR.message_types_by_name['LogMessage'] = _LOGMESSAGE +DESCRIPTOR.message_types_by_name['SessionLog'] = _SESSIONLOG +DESCRIPTOR.message_types_by_name['TaggedRunMetadata'] = _TAGGEDRUNMETADATA +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Event = _reflection.GeneratedProtocolMessageType('Event', (_message.Message,), dict( + DESCRIPTOR = _EVENT, + __module__ = 'tensorboardX.src.event_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.Event) + )) +_sym_db.RegisterMessage(Event) + +LogMessage = _reflection.GeneratedProtocolMessageType('LogMessage', (_message.Message,), dict( + DESCRIPTOR = _LOGMESSAGE, + __module__ = 'tensorboardX.src.event_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.LogMessage) + )) +_sym_db.RegisterMessage(LogMessage) + +SessionLog = _reflection.GeneratedProtocolMessageType('SessionLog', (_message.Message,), dict( + DESCRIPTOR = _SESSIONLOG, + __module__ = 'tensorboardX.src.event_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.SessionLog) + )) +_sym_db.RegisterMessage(SessionLog) + +TaggedRunMetadata = _reflection.GeneratedProtocolMessageType('TaggedRunMetadata', (_message.Message,), dict( + DESCRIPTOR = _TAGGEDRUNMETADATA, + __module__ = 'tensorboardX.src.event_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.TaggedRunMetadata) + )) +_sym_db.RegisterMessage(TaggedRunMetadata) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\023org.tensorflow.utilB\013EventProtosP\001\370\001\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/graph_pb2.py b/tensorboardX/src/graph_pb2.py new file mode 100644 index 0000000..23dda00 --- /dev/null +++ b/tensorboardX/src/graph_pb2.py @@ -0,0 +1,92 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/graph.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import node_def_pb2 as tensorboardX_dot_src_dot_node__def__pb2 +from tensorboardX.src import versions_pb2 as tensorboardX_dot_src_dot_versions__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/graph.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1ctensorboardX/src/graph.proto\x12\x0btensorboard\x1a\x1ftensorboardX/src/node_def.proto\x1a\x1ftensorboardX/src/versions.proto\"n\n\x08GraphDef\x12\"\n\x04node\x18\x01 \x03(\x0b\x32\x14.tensorboard.NodeDef\x12)\n\x08versions\x18\x04 \x01(\x0b\x32\x17.tensorboard.VersionDef\x12\x13\n\x07version\x18\x03 \x01(\x05\x42\x02\x18\x01\x42,\n\x18org.tensorflow.frameworkB\x0bGraphProtosP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_node__def__pb2.DESCRIPTOR,tensorboardX_dot_src_dot_versions__pb2.DESCRIPTOR,]) + + + + +_GRAPHDEF = _descriptor.Descriptor( + name='GraphDef', + full_name='tensorboard.GraphDef', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node', full_name='tensorboard.GraphDef.node', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='versions', full_name='tensorboard.GraphDef.versions', index=1, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='version', full_name='tensorboard.GraphDef.version', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=111, + serialized_end=221, +) + +_GRAPHDEF.fields_by_name['node'].message_type = tensorboardX_dot_src_dot_node__def__pb2._NODEDEF +_GRAPHDEF.fields_by_name['versions'].message_type = tensorboardX_dot_src_dot_versions__pb2._VERSIONDEF +DESCRIPTOR.message_types_by_name['GraphDef'] = _GRAPHDEF +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +GraphDef = _reflection.GeneratedProtocolMessageType('GraphDef', (_message.Message,), dict( + DESCRIPTOR = _GRAPHDEF, + __module__ = 'tensorboardX.src.graph_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.GraphDef) + )) +_sym_db.RegisterMessage(GraphDef) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\013GraphProtosP\001\370\001\001')) +_GRAPHDEF.fields_by_name['version'].has_options = True +_GRAPHDEF.fields_by_name['version']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/node_def_pb2.py b/tensorboardX/src/node_def_pb2.py new file mode 100644 index 0000000..11ec64e --- /dev/null +++ b/tensorboardX/src/node_def_pb2.py @@ -0,0 +1,151 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/node_def.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import attr_value_pb2 as tensorboardX_dot_src_dot_attr__value__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/node_def.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1ftensorboardX/src/node_def.proto\x12\x0btensorboard\x1a!tensorboardX/src/attr_value.proto\"\xb5\x01\n\x07NodeDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02op\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x03(\t\x12\x0e\n\x06\x64\x65vice\x18\x04 \x01(\t\x12,\n\x04\x61ttr\x18\x05 \x03(\x0b\x32\x1e.tensorboard.NodeDef.AttrEntry\x1a\x43\n\tAttrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.tensorboard.AttrValue:\x02\x38\x01\x42*\n\x18org.tensorflow.frameworkB\tNodeProtoP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_attr__value__pb2.DESCRIPTOR,]) + + + + +_NODEDEF_ATTRENTRY = _descriptor.Descriptor( + name='AttrEntry', + full_name='tensorboard.NodeDef.AttrEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='tensorboard.NodeDef.AttrEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='tensorboard.NodeDef.AttrEntry.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=198, + serialized_end=265, +) + +_NODEDEF = _descriptor.Descriptor( + name='NodeDef', + full_name='tensorboard.NodeDef', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='tensorboard.NodeDef.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='op', full_name='tensorboard.NodeDef.op', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='input', full_name='tensorboard.NodeDef.input', index=2, + number=3, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='device', full_name='tensorboard.NodeDef.device', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='attr', full_name='tensorboard.NodeDef.attr', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_NODEDEF_ATTRENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=84, + serialized_end=265, +) + +_NODEDEF_ATTRENTRY.fields_by_name['value'].message_type = tensorboardX_dot_src_dot_attr__value__pb2._ATTRVALUE +_NODEDEF_ATTRENTRY.containing_type = _NODEDEF +_NODEDEF.fields_by_name['attr'].message_type = _NODEDEF_ATTRENTRY +DESCRIPTOR.message_types_by_name['NodeDef'] = _NODEDEF +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +NodeDef = _reflection.GeneratedProtocolMessageType('NodeDef', (_message.Message,), dict( + + AttrEntry = _reflection.GeneratedProtocolMessageType('AttrEntry', (_message.Message,), dict( + DESCRIPTOR = _NODEDEF_ATTRENTRY, + __module__ = 'tensorboardX.src.node_def_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.NodeDef.AttrEntry) + )) + , + DESCRIPTOR = _NODEDEF, + __module__ = 'tensorboardX.src.node_def_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.NodeDef) + )) +_sym_db.RegisterMessage(NodeDef) +_sym_db.RegisterMessage(NodeDef.AttrEntry) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\tNodeProtoP\001\370\001\001')) +_NODEDEF_ATTRENTRY.has_options = True +_NODEDEF_ATTRENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/plugin_pr_curve_pb2.py b/tensorboardX/src/plugin_pr_curve_pb2.py new file mode 100644 index 0000000..c219d52 --- /dev/null +++ b/tensorboardX/src/plugin_pr_curve_pb2.py @@ -0,0 +1,76 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/plugin_pr_curve.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/plugin_pr_curve.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n&tensorboardX/src/plugin_pr_curve.proto\x12\x0btensorboard\"<\n\x11PrCurvePluginData\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x16\n\x0enum_thresholds\x18\x02 \x01(\rb\x06proto3') +) + + + + +_PRCURVEPLUGINDATA = _descriptor.Descriptor( + name='PrCurvePluginData', + full_name='tensorboard.PrCurvePluginData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='version', full_name='tensorboard.PrCurvePluginData.version', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_thresholds', full_name='tensorboard.PrCurvePluginData.num_thresholds', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=55, + serialized_end=115, +) + +DESCRIPTOR.message_types_by_name['PrCurvePluginData'] = _PRCURVEPLUGINDATA +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PrCurvePluginData = _reflection.GeneratedProtocolMessageType('PrCurvePluginData', (_message.Message,), dict( + DESCRIPTOR = _PRCURVEPLUGINDATA, + __module__ = 'tensorboardX.src.plugin_pr_curve_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.PrCurvePluginData) + )) +_sym_db.RegisterMessage(PrCurvePluginData) + + +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/resource_handle_pb2.py b/tensorboardX/src/resource_handle_pb2.py new file mode 100644 index 0000000..276703e --- /dev/null +++ b/tensorboardX/src/resource_handle_pb2.py @@ -0,0 +1,99 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/resource_handle.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/resource_handle.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n&tensorboardX/src/resource_handle.proto\x12\x0btensorboard\"r\n\x13ResourceHandleProto\x12\x0e\n\x06\x64\x65vice\x18\x01 \x01(\t\x12\x11\n\tcontainer\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\thash_code\x18\x04 \x01(\x04\x12\x17\n\x0fmaybe_type_name\x18\x05 \x01(\tB/\n\x18org.tensorflow.frameworkB\x0eResourceHandleP\x01\xf8\x01\x01\x62\x06proto3') +) + + + + +_RESOURCEHANDLEPROTO = _descriptor.Descriptor( + name='ResourceHandleProto', + full_name='tensorboard.ResourceHandleProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='device', full_name='tensorboard.ResourceHandleProto.device', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='container', full_name='tensorboard.ResourceHandleProto.container', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='name', full_name='tensorboard.ResourceHandleProto.name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='hash_code', full_name='tensorboard.ResourceHandleProto.hash_code', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='maybe_type_name', full_name='tensorboard.ResourceHandleProto.maybe_type_name', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=55, + serialized_end=169, +) + +DESCRIPTOR.message_types_by_name['ResourceHandleProto'] = _RESOURCEHANDLEPROTO +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ResourceHandleProto = _reflection.GeneratedProtocolMessageType('ResourceHandleProto', (_message.Message,), dict( + DESCRIPTOR = _RESOURCEHANDLEPROTO, + __module__ = 'tensorboardX.src.resource_handle_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.ResourceHandleProto) + )) +_sym_db.RegisterMessage(ResourceHandleProto) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\016ResourceHandleP\001\370\001\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/summary_pb2.py b/tensorboardX/src/summary_pb2.py new file mode 100644 index 0000000..db24ea8 --- /dev/null +++ b/tensorboardX/src/summary_pb2.py @@ -0,0 +1,532 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/summary.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import tensor_pb2 as tensorboardX_dot_src_dot_tensor__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/summary.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1etensorboardX/src/summary.proto\x12\x0btensorboard\x1a\x1dtensorboardX/src/tensor.proto\"\'\n\x12SummaryDescription\x12\x11\n\ttype_hint\x18\x01 \x01(\t\"\x87\x01\n\x0eHistogramProto\x12\x0b\n\x03min\x18\x01 \x01(\x01\x12\x0b\n\x03max\x18\x02 \x01(\x01\x12\x0b\n\x03num\x18\x03 \x01(\x01\x12\x0b\n\x03sum\x18\x04 \x01(\x01\x12\x13\n\x0bsum_squares\x18\x05 \x01(\x01\x12\x18\n\x0c\x62ucket_limit\x18\x06 \x03(\x01\x42\x02\x10\x01\x12\x12\n\x06\x62ucket\x18\x07 \x03(\x01\x42\x02\x10\x01\"\x83\x01\n\x0fSummaryMetadata\x12<\n\x0bplugin_data\x18\x01 \x03(\x0b\x32\'.tensorboard.SummaryMetadata.PluginData\x1a\x32\n\nPluginData\x12\x13\n\x0bplugin_name\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\"\xe4\x04\n\x07Summary\x12)\n\x05value\x18\x01 \x03(\x0b\x32\x1a.tensorboard.Summary.Value\x1aX\n\x05Image\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05width\x18\x02 \x01(\x05\x12\x12\n\ncolorspace\x18\x03 \x01(\x05\x12\x1c\n\x14\x65ncoded_image_string\x18\x04 \x01(\x0c\x1a}\n\x05\x41udio\x12\x13\n\x0bsample_rate\x18\x01 \x01(\x02\x12\x14\n\x0cnum_channels\x18\x02 \x01(\x03\x12\x15\n\rlength_frames\x18\x03 \x01(\x03\x12\x1c\n\x14\x65ncoded_audio_string\x18\x04 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x05 \x01(\t\x1a\xd4\x02\n\x05Value\x12\x11\n\tnode_name\x18\x07 \x01(\t\x12\x0b\n\x03tag\x18\x01 \x01(\t\x12.\n\x08metadata\x18\t \x01(\x0b\x32\x1c.tensorboard.SummaryMetadata\x12\x16\n\x0csimple_value\x18\x02 \x01(\x02H\x00\x12&\n\x1cobsolete_old_style_histogram\x18\x03 \x01(\x0cH\x00\x12+\n\x05image\x18\x04 \x01(\x0b\x32\x1a.tensorboard.Summary.ImageH\x00\x12,\n\x05histo\x18\x05 \x01(\x0b\x32\x1b.tensorboard.HistogramProtoH\x00\x12+\n\x05\x61udio\x18\x06 \x01(\x0b\x32\x1a.tensorboard.Summary.AudioH\x00\x12*\n\x06tensor\x18\x08 \x01(\x0b\x32\x18.tensorboard.TensorProtoH\x00\x42\x07\n\x05valueB.\n\x18org.tensorflow.frameworkB\rSummaryProtosP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_tensor__pb2.DESCRIPTOR,]) + + + + +_SUMMARYDESCRIPTION = _descriptor.Descriptor( + name='SummaryDescription', + full_name='tensorboard.SummaryDescription', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type_hint', full_name='tensorboard.SummaryDescription.type_hint', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=78, + serialized_end=117, +) + + +_HISTOGRAMPROTO = _descriptor.Descriptor( + name='HistogramProto', + full_name='tensorboard.HistogramProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='min', full_name='tensorboard.HistogramProto.min', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='max', full_name='tensorboard.HistogramProto.max', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num', full_name='tensorboard.HistogramProto.num', index=2, + number=3, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sum', full_name='tensorboard.HistogramProto.sum', index=3, + number=4, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sum_squares', full_name='tensorboard.HistogramProto.sum_squares', index=4, + number=5, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bucket_limit', full_name='tensorboard.HistogramProto.bucket_limit', index=5, + number=6, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='bucket', full_name='tensorboard.HistogramProto.bucket', index=6, + number=7, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=120, + serialized_end=255, +) + + +_SUMMARYMETADATA_PLUGINDATA = _descriptor.Descriptor( + name='PluginData', + full_name='tensorboard.SummaryMetadata.PluginData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='plugin_name', full_name='tensorboard.SummaryMetadata.PluginData.plugin_name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='content', full_name='tensorboard.SummaryMetadata.PluginData.content', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=339, + serialized_end=389, +) + +_SUMMARYMETADATA = _descriptor.Descriptor( + name='SummaryMetadata', + full_name='tensorboard.SummaryMetadata', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='plugin_data', full_name='tensorboard.SummaryMetadata.plugin_data', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_SUMMARYMETADATA_PLUGINDATA, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=258, + serialized_end=389, +) + + +_SUMMARY_IMAGE = _descriptor.Descriptor( + name='Image', + full_name='tensorboard.Summary.Image', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='height', full_name='tensorboard.Summary.Image.height', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='width', full_name='tensorboard.Summary.Image.width', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='colorspace', full_name='tensorboard.Summary.Image.colorspace', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='encoded_image_string', full_name='tensorboard.Summary.Image.encoded_image_string', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=446, + serialized_end=534, +) + +_SUMMARY_AUDIO = _descriptor.Descriptor( + name='Audio', + full_name='tensorboard.Summary.Audio', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='sample_rate', full_name='tensorboard.Summary.Audio.sample_rate', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_channels', full_name='tensorboard.Summary.Audio.num_channels', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='length_frames', full_name='tensorboard.Summary.Audio.length_frames', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='encoded_audio_string', full_name='tensorboard.Summary.Audio.encoded_audio_string', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='content_type', full_name='tensorboard.Summary.Audio.content_type', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=536, + serialized_end=661, +) + +_SUMMARY_VALUE = _descriptor.Descriptor( + name='Value', + full_name='tensorboard.Summary.Value', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node_name', full_name='tensorboard.Summary.Value.node_name', index=0, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tag', full_name='tensorboard.Summary.Value.tag', index=1, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='metadata', full_name='tensorboard.Summary.Value.metadata', index=2, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='simple_value', full_name='tensorboard.Summary.Value.simple_value', index=3, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='obsolete_old_style_histogram', full_name='tensorboard.Summary.Value.obsolete_old_style_histogram', index=4, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='image', full_name='tensorboard.Summary.Value.image', index=5, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='histo', full_name='tensorboard.Summary.Value.histo', index=6, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='audio', full_name='tensorboard.Summary.Value.audio', index=7, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tensor', full_name='tensorboard.Summary.Value.tensor', index=8, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='tensorboard.Summary.Value.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=664, + serialized_end=1004, +) + +_SUMMARY = _descriptor.Descriptor( + name='Summary', + full_name='tensorboard.Summary', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='tensorboard.Summary.value', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_SUMMARY_IMAGE, _SUMMARY_AUDIO, _SUMMARY_VALUE, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=392, + serialized_end=1004, +) + +_SUMMARYMETADATA_PLUGINDATA.containing_type = _SUMMARYMETADATA +_SUMMARYMETADATA.fields_by_name['plugin_data'].message_type = _SUMMARYMETADATA_PLUGINDATA +_SUMMARY_IMAGE.containing_type = _SUMMARY +_SUMMARY_AUDIO.containing_type = _SUMMARY +_SUMMARY_VALUE.fields_by_name['metadata'].message_type = _SUMMARYMETADATA +_SUMMARY_VALUE.fields_by_name['image'].message_type = _SUMMARY_IMAGE +_SUMMARY_VALUE.fields_by_name['histo'].message_type = _HISTOGRAMPROTO +_SUMMARY_VALUE.fields_by_name['audio'].message_type = _SUMMARY_AUDIO +_SUMMARY_VALUE.fields_by_name['tensor'].message_type = tensorboardX_dot_src_dot_tensor__pb2._TENSORPROTO +_SUMMARY_VALUE.containing_type = _SUMMARY +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['simple_value']) +_SUMMARY_VALUE.fields_by_name['simple_value'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['obsolete_old_style_histogram']) +_SUMMARY_VALUE.fields_by_name['obsolete_old_style_histogram'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['image']) +_SUMMARY_VALUE.fields_by_name['image'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['histo']) +_SUMMARY_VALUE.fields_by_name['histo'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['audio']) +_SUMMARY_VALUE.fields_by_name['audio'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY_VALUE.oneofs_by_name['value'].fields.append( + _SUMMARY_VALUE.fields_by_name['tensor']) +_SUMMARY_VALUE.fields_by_name['tensor'].containing_oneof = _SUMMARY_VALUE.oneofs_by_name['value'] +_SUMMARY.fields_by_name['value'].message_type = _SUMMARY_VALUE +DESCRIPTOR.message_types_by_name['SummaryDescription'] = _SUMMARYDESCRIPTION +DESCRIPTOR.message_types_by_name['HistogramProto'] = _HISTOGRAMPROTO +DESCRIPTOR.message_types_by_name['SummaryMetadata'] = _SUMMARYMETADATA +DESCRIPTOR.message_types_by_name['Summary'] = _SUMMARY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +SummaryDescription = _reflection.GeneratedProtocolMessageType('SummaryDescription', (_message.Message,), dict( + DESCRIPTOR = _SUMMARYDESCRIPTION, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.SummaryDescription) + )) +_sym_db.RegisterMessage(SummaryDescription) + +HistogramProto = _reflection.GeneratedProtocolMessageType('HistogramProto', (_message.Message,), dict( + DESCRIPTOR = _HISTOGRAMPROTO, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.HistogramProto) + )) +_sym_db.RegisterMessage(HistogramProto) + +SummaryMetadata = _reflection.GeneratedProtocolMessageType('SummaryMetadata', (_message.Message,), dict( + + PluginData = _reflection.GeneratedProtocolMessageType('PluginData', (_message.Message,), dict( + DESCRIPTOR = _SUMMARYMETADATA_PLUGINDATA, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.SummaryMetadata.PluginData) + )) + , + DESCRIPTOR = _SUMMARYMETADATA, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.SummaryMetadata) + )) +_sym_db.RegisterMessage(SummaryMetadata) +_sym_db.RegisterMessage(SummaryMetadata.PluginData) + +Summary = _reflection.GeneratedProtocolMessageType('Summary', (_message.Message,), dict( + + Image = _reflection.GeneratedProtocolMessageType('Image', (_message.Message,), dict( + DESCRIPTOR = _SUMMARY_IMAGE, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.Summary.Image) + )) + , + + Audio = _reflection.GeneratedProtocolMessageType('Audio', (_message.Message,), dict( + DESCRIPTOR = _SUMMARY_AUDIO, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.Summary.Audio) + )) + , + + Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( + DESCRIPTOR = _SUMMARY_VALUE, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.Summary.Value) + )) + , + DESCRIPTOR = _SUMMARY, + __module__ = 'tensorboardX.src.summary_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.Summary) + )) +_sym_db.RegisterMessage(Summary) +_sym_db.RegisterMessage(Summary.Image) +_sym_db.RegisterMessage(Summary.Audio) +_sym_db.RegisterMessage(Summary.Value) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\rSummaryProtosP\001\370\001\001')) +_HISTOGRAMPROTO.fields_by_name['bucket_limit'].has_options = True +_HISTOGRAMPROTO.fields_by_name['bucket_limit']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_HISTOGRAMPROTO.fields_by_name['bucket'].has_options = True +_HISTOGRAMPROTO.fields_by_name['bucket']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/tensor_pb2.py b/tensorboardX/src/tensor_pb2.py new file mode 100644 index 0000000..b54d9c1 --- /dev/null +++ b/tensorboardX/src/tensor_pb2.py @@ -0,0 +1,185 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/tensor.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from tensorboardX.src import resource_handle_pb2 as tensorboardX_dot_src_dot_resource__handle__pb2 +from tensorboardX.src import tensor_shape_pb2 as tensorboardX_dot_src_dot_tensor__shape__pb2 +from tensorboardX.src import types_pb2 as tensorboardX_dot_src_dot_types__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/tensor.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1dtensorboardX/src/tensor.proto\x12\x0btensorboard\x1a&tensorboardX/src/resource_handle.proto\x1a#tensorboardX/src/tensor_shape.proto\x1a\x1ctensorboardX/src/types.proto\"\xa6\x03\n\x0bTensorProto\x12$\n\x05\x64type\x18\x01 \x01(\x0e\x32\x15.tensorboard.DataType\x12\x33\n\x0ctensor_shape\x18\x02 \x01(\x0b\x32\x1d.tensorboard.TensorShapeProto\x12\x16\n\x0eversion_number\x18\x03 \x01(\x05\x12\x16\n\x0etensor_content\x18\x04 \x01(\x0c\x12\x14\n\x08half_val\x18\r \x03(\x05\x42\x02\x10\x01\x12\x15\n\tfloat_val\x18\x05 \x03(\x02\x42\x02\x10\x01\x12\x16\n\ndouble_val\x18\x06 \x03(\x01\x42\x02\x10\x01\x12\x13\n\x07int_val\x18\x07 \x03(\x05\x42\x02\x10\x01\x12\x12\n\nstring_val\x18\x08 \x03(\x0c\x12\x18\n\x0cscomplex_val\x18\t \x03(\x02\x42\x02\x10\x01\x12\x15\n\tint64_val\x18\n \x03(\x03\x42\x02\x10\x01\x12\x14\n\x08\x62ool_val\x18\x0b \x03(\x08\x42\x02\x10\x01\x12\x18\n\x0c\x64\x63omplex_val\x18\x0c \x03(\x01\x42\x02\x10\x01\x12=\n\x13resource_handle_val\x18\x0e \x03(\x0b\x32 .tensorboard.ResourceHandleProtoB-\n\x18org.tensorflow.frameworkB\x0cTensorProtosP\x01\xf8\x01\x01\x62\x06proto3') + , + dependencies=[tensorboardX_dot_src_dot_resource__handle__pb2.DESCRIPTOR,tensorboardX_dot_src_dot_tensor__shape__pb2.DESCRIPTOR,tensorboardX_dot_src_dot_types__pb2.DESCRIPTOR,]) + + + + +_TENSORPROTO = _descriptor.Descriptor( + name='TensorProto', + full_name='tensorboard.TensorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='dtype', full_name='tensorboard.TensorProto.dtype', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tensor_shape', full_name='tensorboard.TensorProto.tensor_shape', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='version_number', full_name='tensorboard.TensorProto.version_number', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tensor_content', full_name='tensorboard.TensorProto.tensor_content', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='half_val', full_name='tensorboard.TensorProto.half_val', index=4, + number=13, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='float_val', full_name='tensorboard.TensorProto.float_val', index=5, + number=5, type=2, cpp_type=6, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='double_val', full_name='tensorboard.TensorProto.double_val', index=6, + number=6, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='int_val', full_name='tensorboard.TensorProto.int_val', index=7, + number=7, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='string_val', full_name='tensorboard.TensorProto.string_val', index=8, + number=8, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='scomplex_val', full_name='tensorboard.TensorProto.scomplex_val', index=9, + number=9, type=2, cpp_type=6, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='int64_val', full_name='tensorboard.TensorProto.int64_val', index=10, + number=10, type=3, cpp_type=2, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='bool_val', full_name='tensorboard.TensorProto.bool_val', index=11, + number=11, type=8, cpp_type=7, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='dcomplex_val', full_name='tensorboard.TensorProto.dcomplex_val', index=12, + number=12, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='resource_handle_val', full_name='tensorboard.TensorProto.resource_handle_val', index=13, + number=14, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=154, + serialized_end=576, +) + +_TENSORPROTO.fields_by_name['dtype'].enum_type = tensorboardX_dot_src_dot_types__pb2._DATATYPE +_TENSORPROTO.fields_by_name['tensor_shape'].message_type = tensorboardX_dot_src_dot_tensor__shape__pb2._TENSORSHAPEPROTO +_TENSORPROTO.fields_by_name['resource_handle_val'].message_type = tensorboardX_dot_src_dot_resource__handle__pb2._RESOURCEHANDLEPROTO +DESCRIPTOR.message_types_by_name['TensorProto'] = _TENSORPROTO +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +TensorProto = _reflection.GeneratedProtocolMessageType('TensorProto', (_message.Message,), dict( + DESCRIPTOR = _TENSORPROTO, + __module__ = 'tensorboardX.src.tensor_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.TensorProto) + )) +_sym_db.RegisterMessage(TensorProto) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\014TensorProtosP\001\370\001\001')) +_TENSORPROTO.fields_by_name['half_val'].has_options = True +_TENSORPROTO.fields_by_name['half_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['float_val'].has_options = True +_TENSORPROTO.fields_by_name['float_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['double_val'].has_options = True +_TENSORPROTO.fields_by_name['double_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['int_val'].has_options = True +_TENSORPROTO.fields_by_name['int_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['scomplex_val'].has_options = True +_TENSORPROTO.fields_by_name['scomplex_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['int64_val'].has_options = True +_TENSORPROTO.fields_by_name['int64_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['bool_val'].has_options = True +_TENSORPROTO.fields_by_name['bool_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +_TENSORPROTO.fields_by_name['dcomplex_val'].has_options = True +_TENSORPROTO.fields_by_name['dcomplex_val']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/tensor_shape_pb2.py b/tensorboardX/src/tensor_shape_pb2.py new file mode 100644 index 0000000..4934cb6 --- /dev/null +++ b/tensorboardX/src/tensor_shape_pb2.py @@ -0,0 +1,125 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/tensor_shape.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/tensor_shape.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n#tensorboardX/src/tensor_shape.proto\x12\x0btensorboard\"{\n\x10TensorShapeProto\x12.\n\x03\x64im\x18\x02 \x03(\x0b\x32!.tensorboard.TensorShapeProto.Dim\x12\x14\n\x0cunknown_rank\x18\x03 \x01(\x08\x1a!\n\x03\x44im\x12\x0c\n\x04size\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\tB2\n\x18org.tensorflow.frameworkB\x11TensorShapeProtosP\x01\xf8\x01\x01\x62\x06proto3') +) + + + + +_TENSORSHAPEPROTO_DIM = _descriptor.Descriptor( + name='Dim', + full_name='tensorboard.TensorShapeProto.Dim', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='size', full_name='tensorboard.TensorShapeProto.Dim.size', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='name', full_name='tensorboard.TensorShapeProto.Dim.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=142, + serialized_end=175, +) + +_TENSORSHAPEPROTO = _descriptor.Descriptor( + name='TensorShapeProto', + full_name='tensorboard.TensorShapeProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='dim', full_name='tensorboard.TensorShapeProto.dim', index=0, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='unknown_rank', full_name='tensorboard.TensorShapeProto.unknown_rank', index=1, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_TENSORSHAPEPROTO_DIM, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=52, + serialized_end=175, +) + +_TENSORSHAPEPROTO_DIM.containing_type = _TENSORSHAPEPROTO +_TENSORSHAPEPROTO.fields_by_name['dim'].message_type = _TENSORSHAPEPROTO_DIM +DESCRIPTOR.message_types_by_name['TensorShapeProto'] = _TENSORSHAPEPROTO +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +TensorShapeProto = _reflection.GeneratedProtocolMessageType('TensorShapeProto', (_message.Message,), dict( + + Dim = _reflection.GeneratedProtocolMessageType('Dim', (_message.Message,), dict( + DESCRIPTOR = _TENSORSHAPEPROTO_DIM, + __module__ = 'tensorboardX.src.tensor_shape_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.TensorShapeProto.Dim) + )) + , + DESCRIPTOR = _TENSORSHAPEPROTO, + __module__ = 'tensorboardX.src.tensor_shape_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.TensorShapeProto) + )) +_sym_db.RegisterMessage(TensorShapeProto) +_sym_db.RegisterMessage(TensorShapeProto.Dim) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\021TensorShapeProtosP\001\370\001\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/types_pb2.py b/tensorboardX/src/types_pb2.py new file mode 100644 index 0000000..cd02d02 --- /dev/null +++ b/tensorboardX/src/types_pb2.py @@ -0,0 +1,254 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/types.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/types.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1ctensorboardX/src/types.proto\x12\x0btensorboard*\xc2\x05\n\x08\x44\x61taType\x12\x0e\n\nDT_INVALID\x10\x00\x12\x0c\n\x08\x44T_FLOAT\x10\x01\x12\r\n\tDT_DOUBLE\x10\x02\x12\x0c\n\x08\x44T_INT32\x10\x03\x12\x0c\n\x08\x44T_UINT8\x10\x04\x12\x0c\n\x08\x44T_INT16\x10\x05\x12\x0b\n\x07\x44T_INT8\x10\x06\x12\r\n\tDT_STRING\x10\x07\x12\x10\n\x0c\x44T_COMPLEX64\x10\x08\x12\x0c\n\x08\x44T_INT64\x10\t\x12\x0b\n\x07\x44T_BOOL\x10\n\x12\x0c\n\x08\x44T_QINT8\x10\x0b\x12\r\n\tDT_QUINT8\x10\x0c\x12\r\n\tDT_QINT32\x10\r\x12\x0f\n\x0b\x44T_BFLOAT16\x10\x0e\x12\r\n\tDT_QINT16\x10\x0f\x12\x0e\n\nDT_QUINT16\x10\x10\x12\r\n\tDT_UINT16\x10\x11\x12\x11\n\rDT_COMPLEX128\x10\x12\x12\x0b\n\x07\x44T_HALF\x10\x13\x12\x0f\n\x0b\x44T_RESOURCE\x10\x14\x12\x10\n\x0c\x44T_FLOAT_REF\x10\x65\x12\x11\n\rDT_DOUBLE_REF\x10\x66\x12\x10\n\x0c\x44T_INT32_REF\x10g\x12\x10\n\x0c\x44T_UINT8_REF\x10h\x12\x10\n\x0c\x44T_INT16_REF\x10i\x12\x0f\n\x0b\x44T_INT8_REF\x10j\x12\x11\n\rDT_STRING_REF\x10k\x12\x14\n\x10\x44T_COMPLEX64_REF\x10l\x12\x10\n\x0c\x44T_INT64_REF\x10m\x12\x0f\n\x0b\x44T_BOOL_REF\x10n\x12\x10\n\x0c\x44T_QINT8_REF\x10o\x12\x11\n\rDT_QUINT8_REF\x10p\x12\x11\n\rDT_QINT32_REF\x10q\x12\x13\n\x0f\x44T_BFLOAT16_REF\x10r\x12\x11\n\rDT_QINT16_REF\x10s\x12\x12\n\x0e\x44T_QUINT16_REF\x10t\x12\x11\n\rDT_UINT16_REF\x10u\x12\x15\n\x11\x44T_COMPLEX128_REF\x10v\x12\x0f\n\x0b\x44T_HALF_REF\x10w\x12\x13\n\x0f\x44T_RESOURCE_REF\x10xB,\n\x18org.tensorflow.frameworkB\x0bTypesProtosP\x01\xf8\x01\x01\x62\x06proto3') +) + +_DATATYPE = _descriptor.EnumDescriptor( + name='DataType', + full_name='tensorboard.DataType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='DT_INVALID', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_FLOAT', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_DOUBLE', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT32', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_UINT8', index=4, number=4, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT16', index=5, number=5, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT8', index=6, number=6, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_STRING', index=7, number=7, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_COMPLEX64', index=8, number=8, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT64', index=9, number=9, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_BOOL', index=10, number=10, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT8', index=11, number=11, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QUINT8', index=12, number=12, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT32', index=13, number=13, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_BFLOAT16', index=14, number=14, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT16', index=15, number=15, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QUINT16', index=16, number=16, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_UINT16', index=17, number=17, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_COMPLEX128', index=18, number=18, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_HALF', index=19, number=19, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_RESOURCE', index=20, number=20, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_FLOAT_REF', index=21, number=101, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_DOUBLE_REF', index=22, number=102, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT32_REF', index=23, number=103, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_UINT8_REF', index=24, number=104, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT16_REF', index=25, number=105, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT8_REF', index=26, number=106, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_STRING_REF', index=27, number=107, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_COMPLEX64_REF', index=28, number=108, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_INT64_REF', index=29, number=109, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_BOOL_REF', index=30, number=110, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT8_REF', index=31, number=111, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QUINT8_REF', index=32, number=112, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT32_REF', index=33, number=113, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_BFLOAT16_REF', index=34, number=114, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QINT16_REF', index=35, number=115, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_QUINT16_REF', index=36, number=116, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_UINT16_REF', index=37, number=117, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_COMPLEX128_REF', index=38, number=118, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_HALF_REF', index=39, number=119, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DT_RESOURCE_REF', index=40, number=120, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=46, + serialized_end=752, +) +_sym_db.RegisterEnumDescriptor(_DATATYPE) + +DataType = enum_type_wrapper.EnumTypeWrapper(_DATATYPE) +DT_INVALID = 0 +DT_FLOAT = 1 +DT_DOUBLE = 2 +DT_INT32 = 3 +DT_UINT8 = 4 +DT_INT16 = 5 +DT_INT8 = 6 +DT_STRING = 7 +DT_COMPLEX64 = 8 +DT_INT64 = 9 +DT_BOOL = 10 +DT_QINT8 = 11 +DT_QUINT8 = 12 +DT_QINT32 = 13 +DT_BFLOAT16 = 14 +DT_QINT16 = 15 +DT_QUINT16 = 16 +DT_UINT16 = 17 +DT_COMPLEX128 = 18 +DT_HALF = 19 +DT_RESOURCE = 20 +DT_FLOAT_REF = 101 +DT_DOUBLE_REF = 102 +DT_INT32_REF = 103 +DT_UINT8_REF = 104 +DT_INT16_REF = 105 +DT_INT8_REF = 106 +DT_STRING_REF = 107 +DT_COMPLEX64_REF = 108 +DT_INT64_REF = 109 +DT_BOOL_REF = 110 +DT_QINT8_REF = 111 +DT_QUINT8_REF = 112 +DT_QINT32_REF = 113 +DT_BFLOAT16_REF = 114 +DT_QINT16_REF = 115 +DT_QUINT16_REF = 116 +DT_UINT16_REF = 117 +DT_COMPLEX128_REF = 118 +DT_HALF_REF = 119 +DT_RESOURCE_REF = 120 + + +DESCRIPTOR.enum_types_by_name['DataType'] = _DATATYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\013TypesProtosP\001\370\001\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/src/versions_pb2.py b/tensorboardX/src/versions_pb2.py new file mode 100644 index 0000000..1daceb7 --- /dev/null +++ b/tensorboardX/src/versions_pb2.py @@ -0,0 +1,85 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: tensorboardX/src/versions.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='tensorboardX/src/versions.proto', + package='tensorboard', + syntax='proto3', + serialized_pb=_b('\n\x1ftensorboardX/src/versions.proto\x12\x0btensorboard\"K\n\nVersionDef\x12\x10\n\x08producer\x18\x01 \x01(\x05\x12\x14\n\x0cmin_consumer\x18\x02 \x01(\x05\x12\x15\n\rbad_consumers\x18\x03 \x03(\x05\x42/\n\x18org.tensorflow.frameworkB\x0eVersionsProtosP\x01\xf8\x01\x01\x62\x06proto3') +) + + + + +_VERSIONDEF = _descriptor.Descriptor( + name='VersionDef', + full_name='tensorboard.VersionDef', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='producer', full_name='tensorboard.VersionDef.producer', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='min_consumer', full_name='tensorboard.VersionDef.min_consumer', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bad_consumers', full_name='tensorboard.VersionDef.bad_consumers', index=2, + number=3, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=48, + serialized_end=123, +) + +DESCRIPTOR.message_types_by_name['VersionDef'] = _VERSIONDEF +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +VersionDef = _reflection.GeneratedProtocolMessageType('VersionDef', (_message.Message,), dict( + DESCRIPTOR = _VERSIONDEF, + __module__ = 'tensorboardX.src.versions_pb2' + # @@protoc_insertion_point(class_scope:tensorboard.VersionDef) + )) +_sym_db.RegisterMessage(VersionDef) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030org.tensorflow.frameworkB\016VersionsProtosP\001\370\001\001')) +# @@protoc_insertion_point(module_scope) diff --git a/tensorboardX/summary.py b/tensorboardX/summary.py new file mode 100644 index 0000000..80d3c69 --- /dev/null +++ b/tensorboardX/summary.py @@ -0,0 +1,264 @@ +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""## Generation of summaries. +### Class for writing Summaries +@@FileWriter +@@FileWriterCache +### Summary Ops +@@tensor_summary +@@scalar +@@histogram +@@audio +@@image +@@merge +@@merge_all +## Utilities +@@get_summary_description +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import logging +import re as _re +import bisect +from six import StringIO +from six.moves import range +import numpy as np +# pylint: disable=unused-import +from .src.summary_pb2 import Summary +from .src.summary_pb2 import HistogramProto +from .src.summary_pb2 import SummaryMetadata +from .src.tensor_pb2 import TensorProto +from .src.tensor_shape_pb2 import TensorShapeProto +from .src.plugin_pr_curve_pb2 import PrCurvePluginData +from .x2num import makenp + +_INVALID_TAG_CHARACTERS = _re.compile(r'[^-/\w\.]') + + +def _clean_tag(name): + # In the past, the first argument to summary ops was a tag, which allowed + # arbitrary characters. Now we are changing the first argument to be the node + # name. This has a number of advantages (users of summary ops now can + # take advantage of the tf name scope system) but risks breaking existing + # usage, because a much smaller set of characters are allowed in node names. + # This function replaces all illegal characters with _s, and logs a warning. + # It also strips leading slashes from the name. + if name is not None: + new_name = _INVALID_TAG_CHARACTERS.sub('_', name) + new_name = new_name.lstrip('/') # Remove leading slashes + if new_name != name: + logging.info('Summary name %s is illegal; using %s instead.' % (name, new_name)) + name = new_name + return name + + +def scalar(name, scalar, collections=None): + """Outputs a `Summary` protocol buffer containing a single scalar value. + The generated Summary has a Tensor.proto containing the input Tensor. + Args: + name: A name for the generated node. Will also serve as the series name in + TensorBoard. + tensor: A real numeric Tensor containing a single value. + collections: Optional list of graph collections keys. The new summary op is + added to these collections. Defaults to `[GraphKeys.SUMMARIES]`. + Returns: + A scalar `Tensor` of type `string`. Which contains a `Summary` protobuf. + Raises: + ValueError: If tensor has the wrong shape or type. + """ + name = _clean_tag(name) + scalar = makenp(scalar) + assert(scalar.squeeze().ndim == 0), 'scalar should be 0D' + scalar = float(scalar) + return Summary(value=[Summary.Value(tag=name, simple_value=scalar)]) + + +def histogram(name, values, bins, collections=None): + # pylint: disable=line-too-long + """Outputs a `Summary` protocol buffer with a histogram. + The generated + [`Summary`](https://www.tensorflow.org/code/tensorflow/core/framework/summary.proto) + has one summary value containing a histogram for `values`. + This op reports an `InvalidArgument` error if any value is not finite. + Args: + name: A name for the generated node. Will also serve as a series name in + TensorBoard. + values: A real numeric `Tensor`. Any shape. Values to use to + build the histogram. + collections: Optional list of graph collections keys. The new summary op is + added to these collections. Defaults to `[GraphKeys.SUMMARIES]`. + Returns: + A scalar `Tensor` of type `string`. The serialized `Summary` protocol + buffer. + """ + name = _clean_tag(name) + values = makenp(values) + hist = make_histogram(values.astype(float), bins) + return Summary(value=[Summary.Value(tag=name, histo=hist)]) + + +def make_histogram(values, bins): + """Convert values into a histogram proto using logic from histogram.cc.""" + values = values.reshape(-1) + counts, limits = np.histogram(values, bins=bins) + limits = limits[1:] + + sum_sq = values.dot(values) + return HistogramProto(min=values.min(), + max=values.max(), + num=len(values), + sum=values.sum(), + sum_squares=sum_sq, + bucket_limit=limits, + bucket=counts) + + +def image(tag, tensor): + """Outputs a `Summary` protocol buffer with images. + The summary has up to `max_images` summary values containing images. The + images are built from `tensor` which must be 3-D with shape `[height, width, + channels]` and where `channels` can be: + * 1: `tensor` is interpreted as Grayscale. + * 3: `tensor` is interpreted as RGB. + * 4: `tensor` is interpreted as RGBA. + The `name` in the outputted Summary.Value protobufs is generated based on the + name, with a suffix depending on the max_outputs setting: + * If `max_outputs` is 1, the summary value tag is '*name*/image'. + * If `max_outputs` is greater than 1, the summary value tags are + generated sequentially as '*name*/image/0', '*name*/image/1', etc. + Args: + tag: A name for the generated node. Will also serve as a series name in + TensorBoard. + tensor: A 3-D `uint8` or `float32` `Tensor` of shape `[height, width, + channels]` where `channels` is 1, 3, or 4. + Returns: + A scalar `Tensor` of type `string`. The serialized `Summary` protocol + buffer. + """ + tag = _clean_tag(tag) + tensor = makenp(tensor, 'IMG') + tensor = tensor.astype(np.float32) + tensor = (tensor * 255).astype(np.uint8) + image = make_image(tensor) + return Summary(value=[Summary.Value(tag=tag, image=image)]) + + +def make_image(tensor): + """Convert an numpy representation image to Image protobuf""" + from PIL import Image + height, width, channel = tensor.shape + image = Image.fromarray(tensor) + import io + output = io.BytesIO() + image.save(output, format='PNG') + image_string = output.getvalue() + output.close() + return Summary.Image(height=height, + width=width, + colorspace=channel, + encoded_image_string=image_string) + + +def audio(tag, tensor, sample_rate=44100): + tensor = makenp(tensor) + tensor = tensor.squeeze() + if abs(tensor).max() > 1: + print('warning: audio amplitude out of range, auto clipped.') + tensor = tensor.clip(-1, 1) + assert(tensor.ndim == 1), 'input tensor should be 1 dimensional.' + + tensor_list = [int(32767.0 * x) for x in tensor] + import io + import wave + import struct + fio = io.BytesIO() + Wave_write = wave.open(fio, 'wb') + Wave_write.setnchannels(1) + Wave_write.setsampwidth(2) + Wave_write.setframerate(sample_rate) + tensor_enc = b'' + for v in tensor_list: + tensor_enc += struct.pack(' 127: # wierd, value > 127 breaks protobuf + num_thresholds = 127 + data = compute_curve(labels, predictions, num_thresholds=num_thresholds, weights=weights) + pr_curve_plugin_data = PrCurvePluginData(version=0, num_thresholds=num_thresholds).SerializeToString() + PluginData = [SummaryMetadata.PluginData(plugin_name='pr_curves', content=pr_curve_plugin_data)] + smd = SummaryMetadata(plugin_data=PluginData) + tensor = TensorProto(dtype='DT_FLOAT', + float_val=data.reshape(-1).tolist(), + tensor_shape=TensorShapeProto( + dim=[TensorShapeProto.Dim(size=data.shape[0]), TensorShapeProto.Dim(size=data.shape[1])])) + return Summary(value=[Summary.Value(tag=tag, metadata=smd, tensor=tensor)]) + + +# https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/pr_curve/summary.py +def compute_curve(labels, predictions, num_thresholds=None, weights=None): + _MINIMUM_COUNT = 1e-7 + + if weights is None: + weights = 1.0 + + # Compute bins of true positives and false positives. + bucket_indices = np.int32(np.floor(predictions * (num_thresholds - 1))) + float_labels = labels.astype(np.float) + histogram_range = (0, num_thresholds - 1) + tp_buckets, _ = np.histogram( + bucket_indices, + bins=num_thresholds, + range=histogram_range, + weights=float_labels * weights) + fp_buckets, _ = np.histogram( + bucket_indices, + bins=num_thresholds, + range=histogram_range, + weights=(1.0 - float_labels) * weights) + + # Obtain the reverse cumulative sum. + tp = np.cumsum(tp_buckets[::-1])[::-1] + fp = np.cumsum(fp_buckets[::-1])[::-1] + tn = fp[0] - fp + fn = tp[0] - tp + precision = tp / np.maximum(_MINIMUM_COUNT, tp + fp) + recall = tp / np.maximum(_MINIMUM_COUNT, tp + fn) + return np.stack((tp, fp, tn, fn, precision, recall)) diff --git a/tensorboardX/writer.py b/tensorboardX/writer.py new file mode 100644 index 0000000..3578c1c --- /dev/null +++ b/tensorboardX/writer.py @@ -0,0 +1,485 @@ +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Provides an API for generating Event protocol buffers.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time +import json +import os +from .src import event_pb2 +from .src import summary_pb2 +from .src import graph_pb2 +from .event_file_writer import EventFileWriter +from .summary import scalar, histogram, image, audio, text, pr_curve +from .graph import graph +from .graph_onnx import gg +from .embedding import make_mat, make_sprite, make_tsv, append_pbtxt + + +class SummaryToEventTransformer(object): + """Abstractly implements the SummaryWriter API. + This API basically implements a number of endpoints (add_summary, + add_session_log, etc). The endpoints all generate an event protobuf, which is + passed to the contained event_writer. + @@__init__ + @@add_summary + @@add_session_log + @@add_graph + @@add_meta_graph + @@add_run_metadata + """ + + def __init__(self, event_writer, graph=None, graph_def=None): + """Creates a `SummaryWriter` and an event file. + On construction the summary writer creates a new event file in `logdir`. + This event file will contain `Event` protocol buffers constructed when you + call one of the following functions: `add_summary()`, `add_session_log()`, + `add_event()`, or `add_graph()`. + If you pass a `Graph` to the constructor it is added to + the event file. (This is equivalent to calling `add_graph()` later). + TensorBoard will pick the graph from the file and display it graphically so + you can interactively explore the graph you built. You will usually pass + the graph from the session in which you launched it: + ```python + ...create a graph... + # Launch the graph in a session. + sess = tf.Session() + # Create a summary writer, add the 'graph' to the event file. + writer = tf.summary.FileWriter(, sess.graph) + ``` + Args: + event_writer: An EventWriter. Implements add_event method. + graph: A `Graph` object, such as `sess.graph`. + graph_def: DEPRECATED: Use the `graph` argument instead. + """ + self.event_writer = event_writer + # For storing used tags for session.run() outputs. + self._session_run_tags = {} + # TODO(zihaolucky). pass this an empty graph to check whether it's necessary. + # currently we don't support graph in MXNet using tensorboard. + + def add_summary(self, summary, global_step=None): + """Adds a `Summary` protocol buffer to the event file. + This method wraps the provided summary in an `Event` protocol buffer + and adds it to the event file. + You can pass the result of evaluating any summary op, using + [`Session.run()`](client.md#Session.run) or + [`Tensor.eval()`](framework.md#Tensor.eval), to this + function. Alternatively, you can pass a `tf.Summary` protocol + buffer that you populate with your own data. The latter is + commonly done to report evaluation results in event files. + Args: + summary: A `Summary` protocol buffer, optionally serialized as a string. + global_step: Number. Optional global step value to record with the + summary. + """ + if isinstance(summary, bytes): + summ = summary_pb2.Summary() + summ.ParseFromString(summary) + summary = summ + event = event_pb2.Event(summary=summary) + self._add_event(event, global_step) + + def add_graph_onnx(self, graph): + """Adds a `Graph` protocol buffer to the event file. + """ + event = event_pb2.Event(graph_def=graph.SerializeToString()) + self._add_event(event, None) + + def add_graph(self, graph): + """Adds a `Graph` protocol buffer to the event file. + """ + event = event_pb2.Event(graph_def=graph.SerializeToString()) + self._add_event(event, None) + + def add_session_log(self, session_log, global_step=None): + """Adds a `SessionLog` protocol buffer to the event file. + This method wraps the provided session in an `Event` protocol buffer + and adds it to the event file. + Args: + session_log: A `SessionLog` protocol buffer. + global_step: Number. Optional global step value to record with the + summary. + """ + event = event_pb2.Event(session_log=session_log) + self._add_event(event, global_step) + + def _add_event(self, event, step): + event.wall_time = time.time() + if step is not None: + event.step = int(step) + self.event_writer.add_event(event) + + +class FileWriter(SummaryToEventTransformer): + """Writes `Summary` protocol buffers to event files. + The `FileWriter` class provides a mechanism to create an event file in a + given directory and add summaries and events to it. The class updates the + file contents asynchronously. This allows a training program to call methods + to add data to the file directly from the training loop, without slowing down + training. + @@__init__ + @@add_summary + @@add_session_log + @@add_event + @@add_graph + @@add_run_metadata + @@get_logdir + @@flush + @@close + """ + + def __init__(self, + logdir, + graph=None, + max_queue=10, + flush_secs=120, + graph_def=None): + """Creates a `FileWriter` and an event file. + On construction the summary writer creates a new event file in `logdir`. + This event file will contain `Event` protocol buffers constructed when you + call one of the following functions: `add_summary()`, `add_session_log()`, + `add_event()`, or `add_graph()`. + If you pass a `Graph` to the constructor it is added to + the event file. (This is equivalent to calling `add_graph()` later). + TensorBoard will pick the graph from the file and display it graphically so + you can interactively explore the graph you built. You will usually pass + the graph from the session in which you launched it: + ```python + ...create a graph... + # Launch the graph in a session. + sess = tf.Session() + # Create a summary writer, add the 'graph' to the event file. + writer = tf.summary.FileWriter(, sess.graph) + ``` + The other arguments to the constructor control the asynchronous writes to + the event file: + * `flush_secs`: How often, in seconds, to flush the added summaries + and events to disk. + * `max_queue`: Maximum number of summaries or events pending to be + written to disk before one of the 'add' calls block. + Args: + logdir: A string. Directory where event file will be written. + graph: A `Graph` object, such as `sess.graph`. + max_queue: Integer. Size of the queue for pending events and summaries. + flush_secs: Number. How often, in seconds, to flush the + pending events and summaries to disk. + graph_def: DEPRECATED: Use the `graph` argument instead. + """ + event_writer = EventFileWriter(logdir, max_queue, flush_secs) + super(FileWriter, self).__init__(event_writer, graph, graph_def) + + def get_logdir(self): + """Returns the directory where event file will be written.""" + return self.event_writer.get_logdir() + + def add_event(self, event): + """Adds an event to the event file. + Args: + event: An `Event` protocol buffer. + """ + self.event_writer.add_event(event) + + def flush(self): + """Flushes the event file to disk. + Call this method to make sure that all pending events have been written to + disk. + """ + self.event_writer.flush() + + def close(self): + """Flushes the event file to disk and close the file. + Call this method when you do not need the summary writer anymore. + """ + self.event_writer.close() + + def reopen(self): + """Reopens the EventFileWriter. + Can be called after `close()` to add more events in the same directory. + The events will go into a new events file. + Does nothing if the EventFileWriter was not closed. + """ + self.event_writer.reopen() + + +class SummaryWriter(object): + """Writes `Summary` directly to event files. + The `SummaryWriter` class provides a high-level api to create an event file in a + given directory and add summaries and events to it. The class updates the + file contents asynchronously. This allows a training program to call methods + to add data to the file directly from the training loop, without slowing down + training. + """ + def __init__(self, log_dir=None, comment=''): + """ + Args: + log_dir (string): save location, default is: runs/**CURRENT_DATETIME_HOSTNAME**, which changes after each + run. Use hierarchical folder structure to compare between runs easily. e.g. 'runs/exp1', 'runs/exp2' + comment (string): comment that appends to the default log_dir + """ + if not log_dir: + import socket + from datetime import datetime + current_time = datetime.now().strftime('%b%d_%H-%M-%S') + log_dir = os.path.join('runs', current_time + '_' + socket.gethostname() + comment) + self.file_writer = FileWriter(logdir=log_dir) + v = 1E-12 + buckets = [] + neg_buckets = [] + while v < 1E20: + buckets.append(v) + neg_buckets.append(-v) + v *= 1.1 + self.default_bins = neg_buckets[::-1] + [0] + buckets + self.text_tags = [] + # + self.all_writers = {self.file_writer.get_logdir(): self.file_writer} + self.scalar_dict = {} # {writer_id : [[timestamp, step, value],...],...} + + def __append_to_scalar_dict(self, tag, scalar_value, global_step, + timestamp): + """This adds an entry to the self.scalar_dict datastructure with format + {writer_id : [[timestamp, step, value], ...], ...}. + """ + from .x2num import makenp + if tag not in self.scalar_dict.keys(): + self.scalar_dict[tag] = [] + self.scalar_dict[tag].append([timestamp, global_step, float(makenp(scalar_value))]) + + def add_scalar(self, tag, scalar_value, global_step=None): + """Add scalar data to summary. + + Args: + tag (string): Data identifier + scalar_value (float): Value to save + global_step (int): Global step value to record + """ + self.file_writer.add_summary(scalar(tag, scalar_value), global_step) + self.__append_to_scalar_dict(tag, scalar_value, global_step, time.time()) + + def add_scalars(self, main_tag, tag_scalar_dict, global_step=None): + """Adds many scalar data to summary. + + Args: + tag (string): Data identifier + main_tag (string): The parent name for the tags + tag_scalar_dict (dict): Key-value pair storing the tag and corresponding values + global_step (int): Global step value to record + + Examples:: + + writer.add_scalars('run_14h',{'xsinx':i*np.sin(i/r), + 'xcosx':i*np.cos(i/r), + 'arctanx': numsteps*np.arctan(i/r)}, i) + # This function adds three values to the same scalar plot with the tag + # 'run_14h' in TensorBoard's scalar section. + """ + timestamp = time.time() + fw_logdir = self.file_writer.get_logdir() + for tag, scalar_value in tag_scalar_dict.items(): + fw_tag = fw_logdir + "/" + main_tag + "/" + tag + if fw_tag in self.all_writers.keys(): + fw = self.all_writers[fw_tag] + else: + fw = FileWriter(logdir=fw_tag) + self.all_writers[fw_tag] = fw + fw.add_summary(scalar(main_tag, scalar_value), global_step) + self.__append_to_scalar_dict(fw_tag, scalar_value, global_step, timestamp) + + def export_scalars_to_json(self, path): + """Exports to the given path an ASCII file containing all the scalars written + so far by this instance, with the following format: + {writer_id : [[timestamp, step, value], ...], ...} + """ + with open(path, "w") as f: + json.dump(self.scalar_dict, f) + + def add_histogram(self, tag, values, global_step=None, bins='tensorflow'): + """Add histogram to summary. + + Args: + tag (string): Data identifier + values (numpy.array): Values to build histogram + global_step (int): Global step value to record + bins (string): one of {'tensorflow','auto', 'fd', ...}, this determines how the bins are made. You can find + other options in: https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram.html + """ + if bins == 'tensorflow': + bins = self.default_bins + self.file_writer.add_summary(histogram(tag, values, bins), global_step) + + def add_image(self, tag, img_tensor, global_step=None): + """Add image data to summary. + + Note that this requires the ``pillow`` package. + + Args: + tag (string): Data identifier + img_tensor (torch.Tensor): Image data + global_step (int): Global step value to record + Shape: + img_tensor: :math:`(3, H, W)`. Use ``torchvision.utils.make_grid()`` to prepare it is a good idea. + """ + self.file_writer.add_summary(image(tag, img_tensor), global_step) + + def add_audio(self, tag, snd_tensor, global_step=None, sample_rate=44100): + """Add audio data to summary. + + Args: + tag (string): Data identifier + snd_tensor (torch.Tensor): Sound data + global_step (int): Global step value to record + sample_rate (int): sample rate in Hz + + Shape: + snd_tensor: :math:`(1, L)`. The values should lie between [-1, 1]. + """ + self.file_writer.add_summary(audio(tag, snd_tensor, sample_rate=sample_rate), global_step) + + def add_text(self, tag, text_string, global_step=None): + """Add text data to summary. + + Args: + tag (string): Data identifier + text_string (string): String to save + global_step (int): Global step value to record + + Examples:: + + writer.add_text('lstm', 'This is an lstm', 0) + writer.add_text('rnn', 'This is an rnn', 10) + """ + self.file_writer.add_summary(text(tag, text_string), global_step) + if tag not in self.text_tags: + self.text_tags.append(tag) + extension_dir = self.file_writer.get_logdir() + '/plugins/tensorboard_text/' + if not os.path.exists(extension_dir): + os.makedirs(extension_dir) + with open(extension_dir + 'tensors.json', 'w') as fp: + json.dump(self.text_tags, fp) + + def add_graph_onnx(self, prototxt): + self.file_writer.add_graph_onnx(gg(prototxt)) + + def add_graph(self, model, input_to_model, verbose=False): + # prohibit second call? + # no, let tensorboard handles it and show its warning message. + """Add graph data to summary. + + Args: + model (torch.nn.Module): model to draw. + input_to_model (torch.autograd.Variable): a variable or a tuple of variables to be fed. + + """ + import torch + from distutils.version import LooseVersion + if LooseVersion(torch.__version__) >= LooseVersion("0.3.1"): + pass + else: + if LooseVersion(torch.__version__) >= LooseVersion("0.3.0"): + print('You are using PyTorch==0.3.0, use add_graph_onnx()') + return + if not hasattr(torch.autograd.Variable, 'grad_fn'): + print('add_graph() only supports PyTorch v0.2.') + return + self.file_writer.add_graph(graph(model, input_to_model, verbose)) + + def add_embedding(self, mat, metadata=None, label_img=None, global_step=None, tag='default'): + """Add embedding projector data to summary. + + Args: + mat (torch.Tensor): A matrix which each row is the feature vector of the data point + metadata (list): A list of labels, each element will be convert to string + label_img (torch.Tensor): Images correspond to each data point + global_step (int): Global step value to record + tag (string): Name for the embedding + Shape: + mat: :math:`(N, D)`, where N is number of data and D is feature dimension + + label_img: :math:`(N, C, H, W)` + + Examples:: + + import keyword + import torch + meta = [] + while len(meta)<100: + meta = meta+keyword.kwlist # get some strings + meta = meta[:100] + + for i, v in enumerate(meta): + meta[i] = v+str(i) + + label_img = torch.rand(100, 3, 10, 32) + for i in range(100): + label_img[i]*=i/100.0 + + writer.add_embedding(torch.randn(100, 5), metadata=meta, label_img=label_img) + writer.add_embedding(torch.randn(100, 5), label_img=label_img) + writer.add_embedding(torch.randn(100, 5), metadata=meta) + """ + if global_step is None: + global_step = 0 + # clear pbtxt? + save_path = os.path.join(self.file_writer.get_logdir(), str(global_step).zfill(5)) + try: + os.makedirs(save_path) + except OSError: + print('warning: Embedding dir exists, did you set global_step for add_embedding()?') + if metadata is not None: + assert mat.size(0) == len(metadata), '#labels should equal with #data points' + make_tsv(metadata, save_path) + if label_img is not None: + assert mat.size(0) == label_img.size(0), '#images should equal with #data points' + make_sprite(label_img, save_path) + assert mat.dim() == 2, 'mat should be 2D, where mat.size(0) is the number of data points' + make_mat(mat.tolist(), save_path) + # new funcion to append to the config file a new embedding + append_pbtxt(metadata, label_img, self.file_writer.get_logdir(), str(global_step).zfill(5), tag) + + def add_pr_curve(self, tag, labels, predictions, global_step=None, num_thresholds=127, weights=None): + """Adds precision recall curve. + + Args: + tag (string): Data identifier + labels (torch.Tensor): Ground truth data. Binary label for each element. + predictions (torch.Tensor): The probability that an element be classified as true. Value should in [0, 1] + global_step (int): Global step value to record + num_thresholds (int): Number of thresholds used to draw the curve. + + """ + from .x2num import makenp + labels = makenp(labels) + predictions = makenp(predictions) + self.file_writer.add_summary(pr_curve(tag, labels, predictions, num_thresholds, weights), global_step) + + def close(self): + if self.file_writer is None: + return # ignore double close + self.file_writer.flush() + self.file_writer.close() + for path, writer in self.all_writers.items(): + writer.flush() + writer.close() + self.file_writer = self.all_writers = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff --git a/tensorboardX/x2num.py b/tensorboardX/x2num.py new file mode 100644 index 0000000..f2f89d5 --- /dev/null +++ b/tensorboardX/x2num.py @@ -0,0 +1,90 @@ +# DO NOT alter/distruct/free input object ! + +import numpy as np + + +def makenp(x, modality=None): + # if already numpy, return + if isinstance(x, np.ndarray): + if modality == 'IMG' and x.dtype == np.uint8: + return x.astype(np.float32) / 255.0 + return x + if np.isscalar(x): + return np.array([x]) + if 'torch' in str(type(x)): + return pytorch_np(x, modality) + if 'chainer' in str(type(x)): + return chainer_np(x, modality) + if 'mxnet' in str(type(x)): + return mxnet_np(x, modality) + + +def pytorch_np(x, modality): + import torch + if isinstance(x, torch.autograd.Variable): + x = x.data + x = x.cpu().numpy() + if modality == 'IMG': + x = _prepare_image(x) + return x + + +def theano_np(x): + import theano + pass + + +def caffe2_np(x): + pass + + +def mxnet_np(x, modality): + x = x.asnumpy() + if modality == 'IMG': + x = _prepare_image(x) + return x + + +def chainer_np(x, modality): + import chainer + x = chainer.cuda.to_cpu(x.data) + if modality == 'IMG': + x = _prepare_image(x) + return x + + +def make_grid(I, ncols=8): + assert isinstance(I, np.ndarray), 'plugin error, should pass numpy array here' + assert I.ndim == 4 and I.shape[1] == 3 + nimg = I.shape[0] + H = I.shape[2] + W = I.shape[3] + ncols = min(nimg, ncols) + nrows = int(np.ceil(float(nimg) / ncols)) + canvas = np.zeros((3, H * nrows, W * ncols)) + i = 0 + for y in range(nrows): + for x in range(ncols): + if i >= nimg: + break + canvas[:, y * H:(y + 1) * H, x * W:(x + 1) * W] = I[i] + i = i + 1 + return canvas + + +def _prepare_image(I): + assert isinstance(I, np.ndarray), 'plugin error, should pass numpy array here' + assert I.ndim == 2 or I.ndim == 3 or I.ndim == 4 + if I.ndim == 4: # NCHW + if I.shape[1] == 1: # N1HW + I = np.concatenate((I, I, I), 1) # N3HW + assert I.shape[1] == 3 + I = make_grid(I) # 3xHxW + if I.ndim == 3 and I.shape[0] == 1: # 1xHxW + I = np.concatenate((I, I, I), 0) # 3xHxW + if I.ndim == 2: # HxW + I = np.expand_dims(I, 0) # 1xHxW + I = np.concatenate((I, I, I), 0) # 3xHxW + I = I.transpose(1, 2, 0) + + return I diff --git a/test.py b/test.py index 3e60ae7..f081d52 100644 --- a/test.py +++ b/test.py @@ -15,8 +15,9 @@ from PIL import Image import lib.TestTransforms as transforms from models.ColorVidNet import ColorVidNet from models.FrameColor import frame_colorization -from models.NonlocalNet import VGG19_feature_color_torchversion, WarpNet -from utils.util import batch_lab2rgb_transpose_mc, folder2vid, mkdir_if_not, save_frames, tensor_lab2rgb, uncenter_l +from models.NonlocalNet import VGG19_pytorch, WarpNet +from utils.util import (batch_lab2rgb_transpose_mc, folder2vid, mkdir_if_not, + save_frames, tensor_lab2rgb, uncenter_l) from utils.util_distortion import CenterPad, Normalize, RGB2Lab, ToTensor os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" @@ -36,7 +37,7 @@ def colorize_video(opt, input_path, reference_file, output_path, nonlocal_net, c print("processing the folder:", input_path) path, dirs, filenames = os.walk(input_path).__next__() file_count = len(filenames) - filenames.sort() + filenames.sort(key=lambda f: int("".join(filter(str.isdigit, f) or -1))) # NOTE: resize frames to 216*384 transform = transforms.Compose( @@ -129,6 +130,7 @@ def colorize_video(opt, input_path, reference_file, output_path, nonlocal_net, c # save the frames save_frames(IA_predict_rgb, output_path, index) + # output video video_name = "video.avi" folder2vid(image_folder=output_path, output_dir=output_path, filename=video_name) @@ -145,7 +147,7 @@ if __name__ == "__main__": parser.add_argument("--clip_path", type=str, default="./sample_videos/clips/v32", help="path of input clips") parser.add_argument("--ref_path", type=str, default="./sample_videos/ref/v32", help="path of refernce images") parser.add_argument("--output_path", type=str, default="./sample_videos/output", help="path of output clips") - opt = parser.parse_args(args=[]) + opt = parser.parse_args() opt.gpu_ids = [int(x) for x in opt.gpu_ids.split(",")] cudnn.benchmark = True print("running on GPU", opt.gpu_ids) @@ -156,7 +158,7 @@ if __name__ == "__main__": nonlocal_net = WarpNet(1) colornet = ColorVidNet(7) - vggnet = VGG19_feature_color_torchversion() + vggnet = VGG19_pytorch() vggnet.load_state_dict(torch.load("data/vgg19_conv.pth")) for param in vggnet.parameters(): param.requires_grad = False diff --git a/train.py b/train.py new file mode 100644 index 0000000..349fbc0 --- /dev/null +++ b/train.py @@ -0,0 +1,738 @@ +from __future__ import print_function + +import argparse +import math +import os +import queue +import time + +import cv2 +import torch +import torch.backends.cudnn as cudnn +import torch.nn as nn +import torch.optim as optim +import torchvision.transforms as transform_lib +from torch.utils.data import ConcatDataset, DataLoader, WeightedRandomSampler +from torchvision.transforms import CenterCrop + +import lib.TrainTransforms as transforms +from lib.videoloader import VideosDataset +from lib.videoloader_imagenet import VideosDataset_ImageNet +from models.ColorVidNet import ColorVidNet +from models.ContextualLoss import ContextualLoss, ContextualLoss_forward +from models.FrameColor import frame_colorization +from models.GAN_models import Discriminator_x64 +from models.NonlocalNet import (NonlocalWeightedAverage, VGG19_pytorch, + WarpNet, WeightedAverage, + WeightedAverage_color) +from tensorboardX import SummaryWriter +from utils.util import (batch_lab2rgb_transpose_mc, feature_normalize, l1_loss, + mkdir_if_not, mse_loss, parse, tensor_lab2rgb, + uncenter_l, weighted_l1_loss, weighted_mse_loss) +from utils.util_distortion import (CenterPad_threshold, Normalize, RGB2Lab, + ToTensor) +from utils.util_tensorboard import TBImageRecorder, value_logger +from utils.warping import WarpingLayer + +cv2.setNumThreads(0) + +parser = argparse.ArgumentParser() +parser.add_argument("--data_root", default="/home/zhanbo/remote/video/video_pair3/", type=str) +parser.add_argument("--data_root_imagenet", default="/home/zhanbo/remote/imagenet_ref_pair/", type=str) +parser.add_argument("--gpu_ids", type=str, default="0,1,2,3", help="separate by comma") +parser.add_argument("--workers", type=int, default=8) +parser.add_argument("--batch_size", type=int, default=16) +parser.add_argument("--image_size", type=int, default=[216, 384]) +parser.add_argument("--ic", type=int, default=7) +parser.add_argument("--epoch", type=int, default=40) + +parser.add_argument("--resume_epoch", type=int, default=0) +parser.add_argument("--resume", type=bool, default=False) +parser.add_argument("--load_pretrained_model", type=bool, default=True) + +parser.add_argument("--lr", type=float, default=0.00001) +parser.add_argument("--beta1", type=float, default=0.5) +parser.add_argument("--lr_step", type=int, default=100) +parser.add_argument("--lr_gamma", type=float, default=0.1) + + +parser.add_argument("--checkpoint_dir", type=str, default="checkpoints/default") +parser.add_argument("--tb_log_step", type=int, default=50) +parser.add_argument("--print_step", type=int, default=2) + +parser.add_argument("--real_reference_probability", type=float, default=0.7) +parser.add_argument("--nonzero_placeholder_probability", type=float, default=0.0) +parser.add_argument("--with_bad", type=bool, default=True) +parser.add_argument("--with_mid", type=bool, default=True) + +parser.add_argument("--domain_invariant", type=bool, default=False) +parser.add_argument("--weigth_l1", type=float, default=2.0) +parser.add_argument("--weight_contextual", type=float, default="0.2") +parser.add_argument("--weight_perceptual", type=float, default="0.001") +parser.add_argument("--weight_smoothness", type=float, default="5.0") +parser.add_argument("--weight_gan", type=float, default="0.2") +parser.add_argument("--weight_nonlocal_smoothness", type=float, default="0.0") +parser.add_argument("--weight_nonlocal_consistent", type=float, default="0.0") +parser.add_argument("--weight_consistent", type=float, default="0.02") +parser.add_argument("--luminance_noise", type=float, default="2.0") +parser.add_argument("--permute_data", type=bool, default=True) +parser.add_argument("--contextual_loss_direction", type=str, default="forward", help="forward or backward matching") + + +def image_logger_fn( + I_last_lab, + I_current_lab, + I_reference_lab, + I_last_lab_predict, + I_current_lab_predict, + I_last_nonlocal_lab, + I_current_nonlocal_lab, +): + I_last_image = batch_lab2rgb_transpose_mc(I_last_lab[0:32, 0:1, :, :], I_last_lab[0:32, 1:3, :, :]) + I_current_image = batch_lab2rgb_transpose_mc(I_current_lab[0:32, 0:1, :, :], I_current_lab[0:32, 1:3, :, :]) + I_reference_image = batch_lab2rgb_transpose_mc(I_reference_lab[0:32, 0:1, :, :], I_reference_lab[0:32, 1:3, :, :]) + I_last_image_predict = batch_lab2rgb_transpose_mc( + I_last_lab_predict[0:32, 0:1, :, :], I_last_lab_predict[0:32, 1:3, :, :] + ) + I_current_image_predict = batch_lab2rgb_transpose_mc( + I_current_lab_predict[0:32, 0:1, :, :], I_current_lab_predict[0:32, 1:3, :, :] + ) + I_last_nonlocal_image = batch_lab2rgb_transpose_mc( + I_last_nonlocal_lab[0:32, 0:1, :, :], I_last_nonlocal_lab[0:32, 1:3, :, :] + ) + I_current_nonlocal_image = batch_lab2rgb_transpose_mc( + I_current_nonlocal_lab[0:32, 0:1, :, :], I_current_nonlocal_lab[0:32, 1:3, :, :] + ) + + img_info = {} + img_info["1_I_last"] = I_last_image + img_info["2_I_current"] = I_current_image + img_info["3_I_reference"] = I_reference_image + img_info["4_I_last_predict"] = I_last_image_predict + img_info["5_I_curren_predict"] = I_current_image_predict + img_info["6_I_last_nonlocal"] = I_last_nonlocal_image + img_info["7_I_current_nonlocal"] = I_current_nonlocal_image + + return img_info + + +def training_logger(): + try: + if total_iter % opt.print_step == 0: + print("processing time:", elapsed) + print( + "Epoch %d, Step[%d/%d], lr: %f, total_loss: %.2f" + % ( + epoch, + ((iter + 1) % iter_num_per_epoch), + iter_num_per_epoch, + step_optim_scheduler_g.get_last_lr()[0], + total_loss.item(), + ) + ) + + value_logger( + tb_writer, + total_iter, + loss_info={ + "l1_loss": l1_loss.item(), + "feat_loss": feat_loss.item(), + "contextual_loss_total": contextual_loss_total.item(), + "smoothness_loss": smoothness_loss.item(), + "nonlocal_smoothness_loss": nonlocal_smoothness_loss.item(), + "nonlocal_consistent_loss": nonlocal_consistent_loss.item(), + "consistent_loss": consistent_loss.item(), + "generator_loss": generator_loss.item(), + "discriminator_loss": discriminator_loss.item(), + "total_loss": total_loss.item(), + }, + ) + + if total_iter % opt.tb_log_step == 0: + I_last_nonlocal_lab = torch.cat( + (I_last_lab[:, 0:1, :, :], I_last_nonlocal_lab_predict[:, 1:3, :, :]), dim=1 + ) + I_current_nonlocal_lab = torch.cat( + (I_current_lab[:, 0:1, :, :], I_current_nonlocal_lab_predict[:, 1:3, :, :]), dim=1 + ) + I_last_lab_predict = torch.cat((I_last_l, I_last_ab_predict), dim=1) + data_queue.put( + ( + ( + I_last_lab.cpu(), + I_current_lab.cpu(), + I_reference_lab.cpu(), + I_last_lab_predict.cpu(), + I_current_lab_predict.cpu(), + I_last_nonlocal_lab.cpu(), + I_current_nonlocal_lab, + ), + total_iter, + ) + ) + + if total_iter % 2000 == 0: + if len(opt.gpu_ids) > 1: + torch.save( + nonlocal_net.module.state_dict(), + os.path.join(opt.checkpoint_dir, "nonlocal_net_iter_%d.pth") % total_iter, + ) + torch.save( + colornet.module.state_dict(), + os.path.join(opt.checkpoint_dir, "colornet_iter_%d.pth") % total_iter, + ) + torch.save( + discriminator.module.state_dict(), + os.path.join(opt.checkpoint_dir, "discriminator_iter_%d.pth") % total_iter, + ) + else: + torch.save( + nonlocal_net.state_dict(), + os.path.join(opt.checkpoint_dir, "nonlocal_net_iter_%d.pth") % total_iter, + ) + torch.save(colornet.state_dict(), os.path.join(opt.checkpoint_dir, "colornet_iter_%d.pth") % total_iter) + torch.save( + discriminator.state_dict(), + os.path.join(opt.checkpoint_dir, "discriminator_iter_%d.pth") % total_iter, + ) + + # save the state for resume + if total_iter % 2000 == 0: + print("saving the checkpoint") + if len(opt.gpu_ids) > 1: + state = { + "total_iter": total_iter + 1, + "epoch": epoch, + "colornet_state": colornet.module.state_dict(), + "nonlocal_net_state": nonlocal_net.module.state_dict(), + "discriminator_state": discriminator.module.state_dict(), + "optimizer_g": optimizer_g.state_dict(), + "optimizer_d": optimizer_d.state_dict(), + "optimizer_schedule_g": step_optim_scheduler_g.state_dict(), + "optimizer_schedule_d": step_optim_scheduler_g.state_dict(), + } + else: + state = { + "total_iter": total_iter + 1, + "epoch": epoch, + "colornet_state": colornet.state_dict(), + "nonlocal_net_state": nonlocal_net.state_dict(), + "discriminator_state": discriminator.state_dict(), + "optimizer_g": optimizer_g.state_dict(), + "optimizer_d": optimizer_d.state_dict(), + "optimizer_schedule_g": step_optim_scheduler_g.state_dict(), + "optimizer_schedule_d": step_optim_scheduler_d.state_dict(), + } + torch.save(state, os.path.join(opt.checkpoint_dir, "learning_checkpoint.pth")) + except Exception as e: + print("Exception during output") + print(e) + + +def gpu_setup(): + os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" + cudnn.benchmark = True + opt.gpu_ids = [int(x) for x in opt.gpu_ids.split(",")] + torch.cuda.set_device(opt.gpu_ids[0]) + device = torch.device("cuda") + print("running on GPU", opt.gpu_ids) + return device + + +def load_data(): + print("initializing dataloader") + transforms_video = [ + CenterCrop(opt.image_size), + RGB2Lab(), + ToTensor(), + Normalize(), + ] + transforms_imagenet = [CenterPad_threshold(opt.image_size), RGB2Lab(), ToTensor(), Normalize()] + extra_reference_transform = [ + transform_lib.RandomHorizontalFlip(0.5), + transform_lib.RandomResizedCrop(480, (0.98, 1.0), ratio=(0.8, 1.2)), + ] + train_dataset_video = VideosDataset( + data_root=opt.data_root, + epoch=opt.epoch, + image_size=opt.image_size, + image_transform=transforms.Compose(transforms_video), + real_reference_probability=opt.real_reference_probability, + nonzero_placeholder_probability=opt.nonzero_placeholder_probability, + ) + train_dataset_imagenet = VideosDataset_ImageNet( + data_root=opt.data_root_imagenet, + image_size=opt.image_size, + epoch=opt.epoch, + with_bad=opt.with_bad, + with_mid=opt.with_mid, + transforms_imagenet=transforms_imagenet, + distortion_level=4, + brightnessjitter=5, + nonzero_placeholder_probability=opt.nonzero_placeholder_probability, + extra_reference_transform=extra_reference_transform, + real_reference_probability=opt.real_reference_probability, + ) + + video_training_length = len(train_dataset_video) + imagenet_training_length = len(train_dataset_imagenet) + dataset_training_length = train_dataset_video.real_len + train_dataset_imagenet.real_len + dataset_combined = ConcatDataset([train_dataset_video, train_dataset_imagenet]) + sampler = WeightedRandomSampler( + [1] * video_training_length + [1] * imagenet_training_length, dataset_training_length * opt.epoch + ) + data_loader = DataLoader( + dataset_combined, + batch_size=opt.batch_size, + shuffle=False, + num_workers=opt.workers, + pin_memory=True, + drop_last=True, + sampler=sampler, + ) + return dataset_training_length, train_dataset_video, train_dataset_imagenet, data_loader + + +def define_loss(): + print("defining loss") + ab_criterion = nn.SmoothL1Loss().to(device) + nonlocal_criterion = nn.SmoothL1Loss().to(device) + feat_l2_criterion = nn.MSELoss().to(device) + feat_l1_criterion = nn.SmoothL1Loss().to(device) + contextual_loss = ContextualLoss().to(device) + contextual_forward_loss = ContextualLoss_forward().to(device) + BCE_stable = nn.BCEWithLogitsLoss().to(device) + return contextual_loss, contextual_forward_loss + + +def define_optimizer(): + print("defining optimizer") + optimizer_g = optim.Adam( + [{"params": nonlocal_net.parameters(), "lr": 1e-5}, {"params": colornet.parameters(), "lr": 2e-4}], + betas=(0.5, 0.999), + eps=1e-5, + amsgrad=True, + ) + optimizer_d = optim.Adam( + filter(lambda p: p.requires_grad, discriminator.parameters()), lr=2 * 1e-4, betas=(0.5, 0.999) + ) + return optimizer_g, optimizer_d + + +def resume_model(): + print("resuming the learning") + checkpoint = torch.load(os.path.join(opt.checkpoint_dir, "learning_checkpoint.pth")) + total_iter = checkpoint["total_iter"] + epoch = checkpoint["epoch"] + colornet.load_state_dict(checkpoint["colornet_state"]) + nonlocal_net.load_state_dict(checkpoint["nonlocal_net_state"]) + discriminator.load_state_dict(checkpoint["discriminator_state"]) + optimizer_g.load_state_dict(checkpoint["optimizer_g"]) + optimizer_d.load_state_dict(checkpoint["optimizer_d"]) + step_optim_scheduler_g.load_state_dict(checkpoint["optimizer_schedule_g"]) + step_optim_scheduler_d.load_state_dict(checkpoint["optimizer_schedule_d"]) + + +def to_device( + colornet, + nonlocal_net, + discriminator, + vggnet, + contextual_loss, + contextual_forward_loss, + weighted_layer_color, + nonlocal_weighted_layer, + warping_layer, + instancenorm, +): + print("moving models to device") + colornet = torch.nn.DataParallel(colornet.to(device), device_ids=opt.gpu_ids) + nonlocal_net = torch.nn.DataParallel(nonlocal_net.to(device), device_ids=opt.gpu_ids) + discriminator = torch.nn.DataParallel(discriminator.to(device), device_ids=opt.gpu_ids) + vggnet = torch.nn.DataParallel(vggnet.to(device), device_ids=opt.gpu_ids) + contextual_loss = torch.nn.DataParallel(contextual_loss.to(device), device_ids=opt.gpu_ids) + contextual_forward_loss = torch.nn.DataParallel(contextual_forward_loss.to(device), device_ids=opt.gpu_ids) + weighted_layer_color = torch.nn.DataParallel(weighted_layer_color.to(device), device_ids=opt.gpu_ids) + nonlocal_weighted_layer = torch.nn.DataParallel(nonlocal_weighted_layer.to(device), device_ids=opt.gpu_ids) + warping_layer = torch.nn.DataParallel(warping_layer.to(device), device_ids=opt.gpu_ids) + instancenorm = torch.nn.DataParallel(instancenorm.to(device), device_ids=opt.gpu_ids) + + return ( + vggnet, + nonlocal_net, + colornet, + discriminator, + instancenorm, + contextual_loss, + contextual_forward_loss, + weighted_layer_color, + nonlocal_weighted_layer, + warping_layer, + ) + + +def loss_init(): + print("initializing losses") + zero_loss = torch.Tensor([0]).to(device) + ( + feat_loss, + contextual_loss_total, + smoothness_loss, + nonlocal_smoothness_loss, + consistent_loss, + nonlocal_consistent_loss, + generator_loss, + discriminator_loss, + ) = (zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss) + + return ( + feat_loss, + contextual_loss_total, + smoothness_loss, + nonlocal_smoothness_loss, + consistent_loss, + nonlocal_consistent_loss, + generator_loss, + discriminator_loss, + ) + + +def video_colorization(): + # colorization for the last frame + I_last_ab_predict, I_last_nonlocal_lab_predict, features_last_gray = frame_colorization( + I_last_lab, + I_reference_lab, + placeholder_lab, + features_B, + vggnet, + nonlocal_net, + colornet, + feature_noise=0, + luminance_noise=opt.luminance_noise, + ) + I_last_lab_predict = torch.cat((I_last_l, I_last_ab_predict), dim=1) + + # colorization for the current frame + I_current_ab_predict, I_current_nonlocal_lab_predict, features_current_gray = frame_colorization( + I_current_lab, + I_reference_lab, + I_last_lab_predict, + features_B, + vggnet, + nonlocal_net, + colornet, + feature_noise=0, + luminance_noise=opt.luminance_noise, + ) + I_current_lab_predict = torch.cat((I_last_l, I_current_ab_predict), dim=1) + return I_current_ab_predict, I_last_ab_predict, I_current_nonlocal_lab_predict, I_last_nonlocal_lab_predict + + +if __name__ == "__main__": + torch.multiprocessing.set_start_method("spawn", force=True) + opt = parse(parser) + opt.data_root = opt.data_root.split(",")[0] + opt.data_root_imagenet = opt.data_root_imagenet.split(",")[0] + mkdir_if_not(opt.checkpoint_dir) + mkdir_if_not("./runs/") + + device = gpu_setup() + dataset_training_length, train_dataset_video, train_dataset_imagenet, data_loader = load_data() + tb_writer = SummaryWriter() + data_queue = queue.Queue() + tb_image_reorder = TBImageRecorder(tb_writer, image_logger_fn, data_queue) + tb_image_reorder.start() + + # define network + nonlocal_net = WarpNet(opt.batch_size) + colornet = ColorVidNet(opt.ic) + discriminator = Discriminator_x64(ndf=64) + colornet.to(device) + nonlocal_net.to(device) + discriminator.to(device) + + weighted_layer = WeightedAverage() + weighted_layer_color = WeightedAverage_color() + nonlocal_weighted_layer = NonlocalWeightedAverage() + instancenorm = nn.InstanceNorm2d(512, affine=False) + warping_layer = WarpingLayer("gpu") + + vggnet = VGG19_pytorch() + vggnet.load_state_dict(torch.load("data/vgg19_conv.pth")) + vggnet.eval() + for param in vggnet.parameters(): + param.requires_grad = False + + # load pre-trained model + if opt.load_pretrained_model: + nonlocal_pretain_path = os.path.join("checkpoints/video_moredata_l1/", "nonlocal_net_iter_76000.pth") + nonlocal_net.load_state_dict(torch.load(nonlocal_pretain_path)) + color_test_path = "checkpoints/video_moredata_l1/" + "colornet_iter_76000.pth" + colornet.load_state_dict(torch.load(color_test_path)) + + # define loss function + contextual_loss, contextual_forward_loss = define_loss() + + # define optimizer + optimizer_g, optimizer_d = define_optimizer() + step_optim_scheduler_g = optim.lr_scheduler.StepLR(optimizer_g, step_size=opt.lr_step, gamma=opt.lr_gamma) + step_optim_scheduler_d = optim.lr_scheduler.StepLR(optimizer_d, step_size=opt.lr_step, gamma=opt.lr_gamma) + + # define others + downsampling_by2 = nn.AvgPool2d(kernel_size=2).to(device) + downsampling_by4 = nn.AvgPool2d(kernel_size=4).to(device) + + # dataset info + iter_num_per_epoch = dataset_training_length // opt.batch_size + total_iter = opt.resume_epoch * iter_num_per_epoch + print( + "train_dataset info, real_len: %d, epoch_len: %d, iter_num_per_epoch: %d" + % (dataset_training_length, len(train_dataset_video) + len(train_dataset_imagenet), iter_num_per_epoch) + ) + + if opt.resume: + resume_model() + + # move to GPU processing + device = opt.gpu_ids[0] + ( + vggnet, + nonlocal_net, + colornet, + discriminator, + instancenorm, + contextual_loss, + contextual_forward_loss, + weighted_layer_color, + nonlocal_weighted_layer, + warping_layer, + ) = to_device( + colornet, + nonlocal_net, + discriminator, + vggnet, + contextual_loss, + contextual_forward_loss, + weighted_layer_color, + nonlocal_weighted_layer, + warping_layer, + instancenorm, + ) + + ( + feat_loss, + contextual_loss_total, + smoothness_loss, + nonlocal_smoothness_loss, + consistent_loss, + nonlocal_consistent_loss, + generator_loss, + discriminator_loss, + ) = loss_init() + + # %% Training + print("start training") + for iter, data in enumerate(data_loader): + start_time = time.time() + total_iter += 1 + epoch = math.ceil(total_iter / iter_num_per_epoch) + + ###### LOADING DATA SAMPLE ###### + ( + I_last_lab, + I_current_lab, + I_reference_lab, + flow_forward, + flow_backward, + mask, + placeholder_lab, + self_ref_flag, + ) = data + I_last_lab = I_last_lab.to(device) + I_current_lab = I_current_lab.to(device) + I_reference_lab = I_reference_lab.to(device) + flow_forward = flow_forward.to(device) + flow_backward = flow_backward.to(device) + mask = mask.to(device) + placeholder_lab = placeholder_lab.to(device) + self_ref_flag = self_ref_flag.to(device) + + I_last_l = I_last_lab[:, 0:1, :, :] + I_last_ab = I_last_lab[:, 1:3, :, :] + I_current_l = I_current_lab[:, 0:1, :, :] + I_current_ab = I_current_lab[:, 1:3, :, :] + I_reference_l = I_reference_lab[:, 0:1, :, :] + I_reference_ab = I_reference_lab[:, 1:3, :, :] + I_reference_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_reference_l), I_reference_ab), dim=1)) + features_B = vggnet(I_reference_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True) + + ###### COLORIZATION ###### + ( + I_current_ab_predict, + I_last_ab_predict, + I_current_nonlocal_lab_predict, + I_last_nonlocal_lab_predict, + ) = video_colorization() + + ###### UPDATE DISCRIMINATOR ###### + if opt.weight_gan > 0: + optimizer_g.zero_grad() + optimizer_d.zero_grad() + + fake_data_lab = torch.cat( + (uncenter_l(I_current_l), I_current_ab_predict, uncenter_l(I_last_l), I_last_ab_predict), dim=1 + ) + real_data_lab = torch.cat((uncenter_l(I_current_l), I_current_ab, uncenter_l(I_last_l), I_last_ab), dim=1) + + if opt.permute_data: + batch_index = torch.arange(-1, opt.batch_size - 1, dtype=torch.long) + real_data_lab = real_data_lab[batch_index, ...] + + y_pred_fake, feature_pred_fake = discriminator(fake_data_lab.detach()) + y_pred_real, feature_pred_real = discriminator(real_data_lab.detach()) + + y = torch.ones_like(y_pred_real) + y2 = torch.zeros_like(y_pred_real) + discriminator_loss = ( + torch.mean((y_pred_real - torch.mean(y_pred_fake) - y) ** 2) + + torch.mean((y_pred_fake - torch.mean(y_pred_real) + y) ** 2) + ) / 2 + discriminator_loss.backward() + optimizer_d.step() + + ###### UPDATE GENERATOR ###### + optimizer_g.zero_grad() + optimizer_d.zero_grad() + + # extract vgg features for both output and original image + I_predict_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_current_l), I_current_ab_predict), dim=1)) + predict_relu1_1, predict_relu2_1, predict_relu3_1, predict_relu4_1, predict_relu5_1 = vggnet( + I_predict_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True + ) + + I_current_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_current_l), I_current_ab), dim=1)) + A_relu1_1, A_relu2_1, A_relu3_1, A_relu4_1, A_relu5_1 = vggnet( + I_current_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True + ) + B_relu1_1, B_relu2_1, B_relu3_1, B_relu4_1, B_relu5_1 = features_B + + ###### LOSS COMPUTE ###### + # l1 loss + if opt.weigth_l1 > 0: + sample_weights = (self_ref_flag[:, 1:3, :, :]) / (sum(self_ref_flag[:, 0, 0, 0]) + 1e-5) + l1_loss = weighted_l1_loss(I_current_ab_predict, I_current_ab, sample_weights) * opt.weigth_l1 + + # generator loss + if opt.weight_gan > 0: + y_pred_fake, feature_pred_fake = discriminator(fake_data_lab) + y_pred_real, feature_pred_real = discriminator(real_data_lab) + generator_loss = ( + ( + torch.mean((y_pred_real - torch.mean(y_pred_fake) + y) ** 2) + + torch.mean((y_pred_fake - torch.mean(y_pred_real) - y) ** 2) + ) + / 2 + * opt.weight_gan + ) + + # feature loss + if opt.domain_invariant: + feat_loss = ( + mse_loss(instancenorm(predict_relu5_1), instancenorm(A_relu5_1.detach())) + * opt.weight_perceptual + * 1e5 + * 0.2 + ) + else: + feat_loss = mse_loss(predict_relu5_1, A_relu5_1.detach()) * opt.weight_perceptual + + # contextual loss + if opt.contextual_loss_direction == "backward": + contextual_style5_1 = torch.mean(contextual_loss(predict_relu5_1, B_relu5_1.detach())) * 8 + contextual_style4_1 = torch.mean(contextual_loss(predict_relu4_1, B_relu4_1.detach())) * 4 + contextual_style3_1 = ( + torch.mean(contextual_loss(downsampling_by2(predict_relu3_1), downsampling_by2(B_relu3_1.detach()))) * 2 + ) + else: + contextual_style5_1 = torch.mean(contextual_forward_loss(predict_relu5_1, B_relu5_1.detach())) * 8 + contextual_style4_1 = torch.mean(contextual_forward_loss(predict_relu4_1, B_relu4_1.detach())) * 4 + contextual_style3_1 = ( + torch.mean( + contextual_forward_loss(downsampling_by2(predict_relu3_1), downsampling_by2(B_relu3_1.detach())) + ) + * 2 + ) + if opt.weight_contextual > 0: + contextual_loss_total = ( + contextual_style5_1 + contextual_style4_1 + contextual_style3_1 + ) * opt.weight_contextual + + # smoothness loss + if opt.weight_smoothness > 0: + scale_factor = 1 + I_current_lab_predict = torch.cat((I_current_l, I_current_ab_predict), dim=1) + IA_ab_weighed = weighted_layer_color( + I_current_lab, I_current_lab_predict, patch_size=3, alpha=10, scale_factor=scale_factor + ) + smoothness_loss = ( + mse_loss(nn.functional.interpolate(I_current_ab_predict, scale_factor=scale_factor), IA_ab_weighed) + * opt.weight_smoothness + ) + + if opt.weight_nonlocal_smoothness > 0: + scale_factor = 0.25 + alpha_nonlocal_smoothness = 0.5 + nonlocal_smooth_feature = feature_normalize(A_relu2_1) + I_current_lab_predict = torch.cat((I_current_l, I_current_ab_predict), dim=1) + I_current_ab_weighted_nonlocal = nonlocal_weighted_layer( + I_current_lab_predict, + nonlocal_smooth_feature.detach(), + patch_size=3, + alpha=alpha_nonlocal_smoothness, + scale_factor=scale_factor, + ) + nonlocal_smoothness_loss = ( + mse_loss( + nn.functional.interpolate(I_current_ab_predict, scale_factor=scale_factor), + I_current_ab_weighted_nonlocal, + ) + * opt.weight_nonlocal_smoothness + ) + + if opt.weight_consistent: + I_current_lab_predict_warp = warping_layer(I_current_lab_predict, flow_forward) + I_current_ab_predict_warp = I_current_lab_predict_warp[:, 1:3, :, :] + consistent_loss = ( + weighted_mse_loss(I_current_ab_predict_warp, I_last_ab_predict, mask) * opt.weight_consistent + ) + + if opt.weight_nonlocal_consistent: + I_current_nonlocal_lab_predict_warp = warping_layer(I_current_nonlocal_lab_predict, flow_forward) + nonlocal_consistent_loss = ( + weighted_mse_loss( + I_current_nonlocal_lab_predict_warp[:, 1:3, :, :], I_last_nonlocal_lab_predict[:, 1:3, :, :], mask + ) + * opt.weight_nonlocal_consistent + ) + + # total loss + total_loss = ( + l1_loss + + feat_loss + + contextual_loss_total + + smoothness_loss + + nonlocal_smoothness_loss + + consistent_loss + + nonlocal_consistent_loss + + generator_loss + ) + total_loss.backward() + optimizer_g.step() + end_time = time.time() + elapsed = end_time - start_time + training_logger() + + step_optim_scheduler_g.step() + step_optim_scheduler_d.step() + + data_queue.put((None, None)) diff --git a/utils/flowlib.py b/utils/flowlib.py new file mode 100644 index 0000000..5dae596 --- /dev/null +++ b/utils/flowlib.py @@ -0,0 +1,523 @@ +#!/usr/bin/python +""" +# ============================== +# flowlib.py +# library for optical flow processing +# Author: Ruoteng Li +# Date: 6th Aug 2016 +# ============================== +""" +import matplotlib.colors as cl +import matplotlib.pyplot as plt +import numpy as np +import png +from PIL import Image + +UNKNOWN_FLOW_THRESH = 1e7 +SMALLFLOW = 0.0 +LARGEFLOW = 1e8 + +""" +============= +Flow Section +============= +""" + + +def show_flow(filename): + """ + visualize optical flow map using matplotlib + :param filename: optical flow file + :return: None + """ + flow = read_flow(filename) + img = flow_to_image(flow) + plt.imshow(img) + plt.show() + + +def visualize_flow(flow, mode="Y"): + """ + this function visualize the input flow + :param flow: input flow in array + :param mode: choose which color mode to visualize the flow (Y: Ccbcr, RGB: RGB color) + :return: None + """ + if mode == "Y": + # Ccbcr color wheel + img = flow_to_image(flow) + plt.imshow(img) + plt.show() + elif mode == "RGB": + (h, w) = flow.shape[0:2] + du = flow[:, :, 0] + dv = flow[:, :, 1] + valid = flow[:, :, 2] + max_flow = max(np.max(du), np.max(dv)) + img = np.zeros((h, w, 3), dtype=np.float64) + # angle layer + img[:, :, 0] = np.arctan2(dv, du) / (2 * np.pi) + # magnitude layer, normalized to 1 + img[:, :, 1] = np.sqrt(du * du + dv * dv) * 8 / max_flow + # phase layer + img[:, :, 2] = 8 - img[:, :, 1] + # clip to [0,1] + small_idx = img[:, :, 0:3] < 0 + large_idx = img[:, :, 0:3] > 1 + img[small_idx] = 0 + img[large_idx] = 1 + # convert to rgb + img = cl.hsv_to_rgb(img) + # remove invalid point + img[:, :, 0] = img[:, :, 0] * valid + img[:, :, 1] = img[:, :, 1] * valid + img[:, :, 2] = img[:, :, 2] * valid + # show + plt.imshow(img) + plt.show() + + return None + + +def read_flow(filename): + """ + read optical flow from Middlebury .flo file + :param filename: name of the flow file + :return: optical flow data in matrix + """ + f = open(filename, "rb") + try: + magic = np.fromfile(f, np.float32, count=1)[0] # For Python3.x + except: + magic = np.fromfile(f, np.float32, count=1) # For Python2.x + data2d = None + + if 202021.25 != magic: + print("Magic number incorrect. Invalid .flo file") + else: + w = np.fromfile(f, np.int32, count=1)[0] + h = np.fromfile(f, np.int32, count=1)[0] + # print("Reading %d x %d flo file" % (h, w)) + data2d = np.fromfile(f, np.float32, count=2 * w * h) + # reshape data into 3D array (columns, rows, channels) + data2d = np.resize(data2d, (h, w, 2)) + f.close() + return data2d + + +def read_flow_png(flow_file): + """ + Read optical flow from KITTI .png file + :param flow_file: name of the flow file + :return: optical flow data in matrix + """ + flow_object = png.Reader(filename=flow_file) + flow_direct = flow_object.asDirect() + flow_data = list(flow_direct[2]) + (w, h) = flow_direct[3]["size"] + flow = np.zeros((h, w, 3), dtype=np.float64) + for i in range(len(flow_data)): + flow[i, :, 0] = flow_data[i][0::3] + flow[i, :, 1] = flow_data[i][1::3] + flow[i, :, 2] = flow_data[i][2::3] + + invalid_idx = flow[:, :, 2] == 0 + flow[:, :, 0:2] = (flow[:, :, 0:2] - 2 ** 15) / 64.0 + flow[invalid_idx, 0] = 0 + flow[invalid_idx, 1] = 0 + return flow + + +def write_flow(flow, filename): + """ + write optical flow in Middlebury .flo format + :param flow: optical flow map + :param filename: optical flow file path to be saved + :return: None + """ + with open(filename, "wb") as f: + magic = np.array([202021.25], dtype=np.float32) + (height, width) = flow.shape[0:2] + w = np.array([width], dtype=np.int32) + h = np.array([height], dtype=np.int32) + magic.tofile(f) + w.tofile(f) + h.tofile(f) + flow.tofile(f) + + +def segment_flow(flow): + h = flow.shape[0] + w = flow.shape[1] + u = flow[:, :, 0] + v = flow[:, :, 1] + + idx = (abs(u) > LARGEFLOW) | (abs(v) > LARGEFLOW) + idx2 = abs(u) == SMALLFLOW + class0 = (v == 0) & (u == 0) + u[idx2] = 0.00001 + tan_value = v / u + + class1 = (tan_value < 1) & (tan_value >= 0) & (u > 0) & (v >= 0) + class2 = (tan_value >= 1) & (u >= 0) & (v >= 0) + class3 = (tan_value < -1) & (u <= 0) & (v >= 0) + class4 = (tan_value < 0) & (tan_value >= -1) & (u < 0) & (v >= 0) + class8 = (tan_value >= -1) & (tan_value < 0) & (u > 0) & (v <= 0) + class7 = (tan_value < -1) & (u >= 0) & (v <= 0) + class6 = (tan_value >= 1) & (u <= 0) & (v <= 0) + class5 = (tan_value >= 0) & (tan_value < 1) & (u < 0) & (v <= 0) + + seg = np.zeros((h, w)) + + seg[class1] = 1 + seg[class2] = 2 + seg[class3] = 3 + seg[class4] = 4 + seg[class5] = 5 + seg[class6] = 6 + seg[class7] = 7 + seg[class8] = 8 + seg[class0] = 0 + seg[idx] = 0 + + return seg + + +def flow_error(tu, tv, u, v): + """ + Calculate average end point error + :param tu: ground-truth horizontal flow map + :param tv: ground-truth vertical flow map + :param u: estimated horizontal flow map + :param v: estimated vertical flow map + :return: End point error of the estimated flow + """ + smallflow = 0.0 + """ + stu = tu[bord+1:end-bord,bord+1:end-bord] + stv = tv[bord+1:end-bord,bord+1:end-bord] + su = u[bord+1:end-bord,bord+1:end-bord] + sv = v[bord+1:end-bord,bord+1:end-bord] + """ + stu = tu[:] + stv = tv[:] + su = u[:] + sv = v[:] + + idxUnknow = (abs(stu) > UNKNOWN_FLOW_THRESH) | (abs(stv) > UNKNOWN_FLOW_THRESH) + stu[idxUnknow] = 0 + stv[idxUnknow] = 0 + su[idxUnknow] = 0 + sv[idxUnknow] = 0 + + ind2 = [(np.absolute(stu) > smallflow) | (np.absolute(stv) > smallflow)] + index_su = su[ind2] + index_sv = sv[ind2] + an = 1.0 / np.sqrt(index_su ** 2 + index_sv ** 2 + 1) + un = index_su * an + vn = index_sv * an + + index_stu = stu[ind2] + index_stv = stv[ind2] + tn = 1.0 / np.sqrt(index_stu ** 2 + index_stv ** 2 + 1) + tun = index_stu * tn + tvn = index_stv * tn + + """ + angle = un * tun + vn * tvn + (an * tn) + index = [angle == 1.0] + angle[index] = 0.999 + ang = np.arccos(angle) + mang = np.mean(ang) + mang = mang * 180 / np.pi + """ + + epe = np.sqrt((stu - su) ** 2 + (stv - sv) ** 2) + epe = epe[ind2] + return np.mean(epe) + + +def flow_to_image(flow, display=False): + """ + Convert flow into middlebury color code image + :param flow: optical flow map + :return: optical flow image in middlebury color + """ + u = flow[:, :, 0] + v = flow[:, :, 1] + + maxu = -999.0 + maxv = -999.0 + minu = 999.0 + minv = 999.0 + + idxUnknow = (abs(u) > UNKNOWN_FLOW_THRESH) | (abs(v) > UNKNOWN_FLOW_THRESH) + u[idxUnknow] = 0 + v[idxUnknow] = 0 + + maxu = max(maxu, np.max(u)) + minu = min(minu, np.min(u)) + + maxv = max(maxv, np.max(v)) + minv = min(minv, np.min(v)) + + rad = np.sqrt(u ** 2 + v ** 2) + maxrad = max(-1, np.max(rad)) + + if display: + print("max flow: %.4f\nflow range:\nu = %.3f .. %.3f\nv = %.3f .. %.3f" % (maxrad, minu, maxu, minv, maxv)) + + u = u / (maxrad + np.finfo(float).eps) + v = v / (maxrad + np.finfo(float).eps) + + img = compute_color(u, v) + + idx = np.repeat(idxUnknow[:, :, np.newaxis], 3, axis=2) + img[idx] = 0 + + return np.uint8(img) + + +def evaluate_flow_file(gt, pred): + """ + evaluate the estimated optical flow end point error according to ground truth provided + :param gt: ground truth file path + :param pred: estimated optical flow file path + :return: end point error, float32 + """ + # Read flow files and calculate the errors + gt_flow = read_flow(gt) # ground truth flow + eva_flow = read_flow(pred) # predicted flow + # Calculate errors + average_pe = flow_error(gt_flow[:, :, 0], gt_flow[:, :, 1], eva_flow[:, :, 0], eva_flow[:, :, 1]) + return average_pe + + +def evaluate_flow(gt_flow, pred_flow): + """ + gt: ground-truth flow + pred: estimated flow + """ + return flow_error( + gt_flow[:, :, 0], + gt_flow[:, :, 1], + pred_flow[:, :, 0], + pred_flow[:, :, 1], + ) + + +""" +============== +Disparity Section +============== +""" + + +def read_disp_png(file_name): + """ + Read optical flow from KITTI .png file + :param file_name: name of the flow file + :return: optical flow data in matrix + """ + image_object = png.Reader(filename=file_name) + image_direct = image_object.asDirect() + image_data = list(image_direct[2]) + (w, h) = image_direct[3]["size"] + channel = len(image_data[0]) / w + flow = np.zeros((h, w, channel), dtype=np.uint16) + for i in range(len(image_data)): + for j in range(channel): + flow[i, :, j] = image_data[i][j::channel] + return flow[:, :, 0] / 256 + + +def disp_to_flowfile(disp, filename): + """ + Read KITTI disparity file in png format + :param disp: disparity matrix + :param filename: the flow file name to save + :return: None + """ + f = open(filename, "wb") + magic = np.array([202021.25], dtype=np.float32) + (height, width) = disp.shape[0:2] + w = np.array([width], dtype=np.int32) + h = np.array([height], dtype=np.int32) + empty_map = np.zeros((height, width), dtype=np.float32) + data = np.dstack((disp, empty_map)) + magic.tofile(f) + w.tofile(f) + h.tofile(f) + data.tofile(f) + f.close() + + +""" +============== +Image Section +============== +""" + + +def read_image(filename): + """ + Read normal image of any format + :param filename: name of the image file + :return: image data in matrix uint8 type + """ + img = Image.open(filename) + return np.array(img) + + +def warp_image(im, flow): + """ + Use optical flow to warp image to the next + :param im: image to warp + :param flow: optical flow + :return: warped image + """ + from scipy import interpolate + + image_height = im.shape[0] + image_width = im.shape[1] + flow_height = flow.shape[0] + flow_width = flow.shape[1] + n = image_height * image_width + (iy, ix) = np.float32(np.mgrid[0:image_height, 0:image_width]) + (fy, fx) = np.float32(np.mgrid[0:flow_height, 0:flow_width]) + fx += flow[:, :, 0].astype(np.float) + fy += flow[:, :, 1] + mask = np.logical_or(fx < 0, fx > flow_width) + mask = np.logical_or(mask, fy < 0) + mask = np.logical_or(mask, fy > flow_height) + fx = np.minimum(np.maximum(fx, 0), flow_width) + fy = np.minimum(np.maximum(fy, 0), flow_height) + points = np.concatenate((ix.reshape(n, 1), iy.reshape(n, 1)), axis=1) + xi = np.concatenate((fx.reshape(n, 1), fy.reshape(n, 1)), axis=1) + warp = np.zeros((image_height, image_width, im.shape[2])) + for i in range(im.shape[2]): + channel = im[:, :, i] + # plt.imshow(channel, cmap='gray') + values = channel.reshape(n, 1) + new_channel = interpolate.griddata(points, values, xi, method="cubic") + new_channel = np.reshape(new_channel, [flow_height, flow_width]) + new_channel[mask] = 1 + warp[:, :, i] = new_channel.astype(np.uint8) + + return warp.astype(np.uint8) + + +""" +============== +Others +============== +""" + + +def scale_image(image, new_range): + """ + Linearly scale the image into desired range + :param image: input image + :param new_range: the new range to be aligned + :return: image normalized in new range + """ + min_val = np.min(image).astype(np.float32) + max_val = np.max(image).astype(np.float32) + min_val_new = np.array(min(new_range), dtype=np.float32) + max_val_new = np.array(max(new_range), dtype=np.float32) + scaled_image = (image - min_val) / (max_val - min_val) * (max_val_new - min_val_new) + min_val_new + return scaled_image.astype(np.uint8) + + +def compute_color(u, v): + """ + compute optical flow color map + :param u: optical flow horizontal map + :param v: optical flow vertical map + :return: optical flow in color code + """ + [h, w] = u.shape + img = np.zeros([h, w, 3]) + nanIdx = np.isnan(u) | np.isnan(v) + u[nanIdx] = 0 + v[nanIdx] = 0 + + colorwheel = make_color_wheel() + ncols = np.size(colorwheel, 0) + + rad = np.sqrt(u ** 2 + v ** 2) + + a = np.arctan2(-v, -u) / np.pi + + fk = (a + 1) / 2 * (ncols - 1) + 1 + + k0 = np.floor(fk).astype(int) + + k1 = k0 + 1 + k1[k1 == ncols + 1] = 1 + f = fk - k0 + + for i in range(np.size(colorwheel, 1)): + tmp = colorwheel[:, i] + col0 = tmp[k0 - 1] / 255 + col1 = tmp[k1 - 1] / 255 + col = (1 - f) * col0 + f * col1 + + idx = rad <= 1 + col[idx] = 1 - rad[idx] * (1 - col[idx]) + notidx = np.logical_not(idx) + + col[notidx] *= 0.75 + img[:, :, i] = np.uint8(np.floor(255 * col * (1 - nanIdx))) + + return img + + +def make_color_wheel(): + """ + Generate color wheel according Middlebury color code + :return: Color wheel + """ + RY = 15 + YG = 6 + GC = 4 + CB = 11 + BM = 13 + MR = 6 + + ncols = RY + YG + GC + CB + BM + MR + + colorwheel = np.zeros([ncols, 3]) + + col = 0 + + # RY + colorwheel[0:RY, 0] = 255 + colorwheel[0:RY, 1] = np.transpose(np.floor(255 * np.arange(0, RY) / RY)) + col += RY + + # YG + colorwheel[col : col + YG, 0] = 255 - np.transpose(np.floor(255 * np.arange(0, YG) / YG)) + colorwheel[col : col + YG, 1] = 255 + col += YG + + # GC + colorwheel[col : col + GC, 1] = 255 + colorwheel[col : col + GC, 2] = np.transpose(np.floor(255 * np.arange(0, GC) / GC)) + col += GC + + # CB + colorwheel[col : col + CB, 1] = 255 - np.transpose(np.floor(255 * np.arange(0, CB) / CB)) + colorwheel[col : col + CB, 2] = 255 + col += CB + + # BM + colorwheel[col : col + BM, 2] = 255 + colorwheel[col : col + BM, 0] = np.transpose(np.floor(255 * np.arange(0, BM) / BM)) + col += +BM + + # MR + colorwheel[col : col + MR, 2] = 255 - np.transpose(np.floor(255 * np.arange(0, MR) / MR)) + colorwheel[col : col + MR, 0] = 255 + + return colorwheel diff --git a/utils/tb_image_recorder.py b/utils/tb_image_recorder.py new file mode 100644 index 0000000..39ee0e4 --- /dev/null +++ b/utils/tb_image_recorder.py @@ -0,0 +1,28 @@ +import threading + +import numpy as np + + +class TBImageRecorder(threading.Thread): + """ + TBImageRecorder + """ + + def __init__(self, tb_writer, func, queue): + super(TBImageRecorder, self).__init__() + self._tb_writer = tb_writer + self._func = func + self._queue = queue + + def run(self): + while True: + msgs, iter_index = self._queue.get() + if msgs: + img_info = self._func(*msgs) + + print("logging the images") + for tag, images in img_info.items(): + if images is not None: + self._tb_writer.add_image(tag, np.clip(images, 0, 255).astype(np.uint8), iter_index) + else: + break diff --git a/utils/util_tensorboard.py b/utils/util_tensorboard.py new file mode 100644 index 0000000..a0e68cc --- /dev/null +++ b/utils/util_tensorboard.py @@ -0,0 +1,43 @@ +import threading + +import numpy as np + +from utils.util import to_np + + +def histogram_logger(tb_writer, iter_index, net=None): + if net is not None: + for tag, value in net.named_parameters(): + tag = tag.replace('.', '/') + tb_writer.add_histogram(tag, to_np(value), iter_index) + tb_writer.add_histogram(tag + '/grad', to_np(value.grad), iter_index) + + +def value_logger(tb_writer, iter_index, loss_info): + for tag, value in loss_info.items(): + tb_writer.add_scalar(tag, value, iter_index) + + +class TBImageRecorder(threading.Thread): + """ + TBImageRecorder + """ + + def __init__(self, tb_writer, func, queue): + super(TBImageRecorder, self).__init__() + self._tb_writer = tb_writer + self._func = func + self._queue = queue + + def run(self): + while True: + msgs, iter_index = self._queue.get() + if msgs: + img_info = self._func(*msgs) + + print("logging the images") + for tag, images in img_info.items(): + if images is not None: + self._tb_writer.add_image(tag, np.clip(images, 0, 255).astype(np.uint8), iter_index) + else: + break diff --git a/utils/warping.py b/utils/warping.py new file mode 100644 index 0000000..7df783d --- /dev/null +++ b/utils/warping.py @@ -0,0 +1,30 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def get_grid(x): + torchHorizontal = ( + torch.linspace(-1.0, 1.0, x.size(3)).view(1, 1, 1, x.size(3)).expand(x.size(0), 1, x.size(2), x.size(3)) + ) + torchVertical = ( + torch.linspace(-1.0, 1.0, x.size(2)).view(1, 1, x.size(2), 1).expand(x.size(0), 1, x.size(2), x.size(3)) + ) + return torch.cat([torchHorizontal, torchVertical], 1).cuda() + + +class WarpingLayer(nn.Module): + def __init__(self, device): + super(WarpingLayer, self).__init__() + self.device = device + + def forward(self, x, flow): + # WarpingLayer uses F.grid_sample, which expects normalized grid + # we still output unnormalized flow for the convenience of comparing EPEs with FlowNet2 and original code + # so here we need to denormalize the flow + flow_for_grip = torch.zeros_like(flow).cuda() + flow_for_grip[:, 0, :, :] = flow[:, 0, :, :] / ((flow.size(3) - 1.0) / 2.0) + flow_for_grip[:, 1, :, :] = flow[:, 1, :, :] / ((flow.size(2) - 1.0) / 2.0) + + grid = (get_grid(x) + flow_for_grip).permute(0, 2, 3, 1) + return F.grid_sample(x, grid, align_corners=True) diff --git a/warping.py b/warping.py new file mode 100644 index 0000000..8cdda79 --- /dev/null +++ b/warping.py @@ -0,0 +1,30 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def get_grid(x): + torchHorizontal = ( + torch.linspace(-1.0, 1.0, x.size(3)).view(1, 1, 1, x.size(3)).expand(x.size(0), 1, x.size(2), x.size(3)) + ) + torchVertical = ( + torch.linspace(-1.0, 1.0, x.size(2)).view(1, 1, x.size(2), 1).expand(x.size(0), 1, x.size(2), x.size(3)) + ) + return torch.cat([torchHorizontal, torchVertical], 1).cuda() + + +class WarpingLayer(nn.Module): + def __init__(self, device): + super(WarpingLayer, self).__init__() + self.device = device + + def forward(self, x, flow): + # WarpingLayer uses F.VGG19_pytorch, which expects normalized grid + # we still output unnormalized flow for the convenience of comparing EPEs with FlowNet2 and original code + # so here we need to denormalize the flow + flow_for_grip = torch.zeros_like(flow).cuda() + flow_for_grip[:, 0, :, :] = flow[:, 0, :, :] / ((flow.size(3) - 1.0) / 2.0) + flow_for_grip[:, 1, :, :] = flow[:, 1, :, :] / ((flow.size(2) - 1.0) / 2.0) + + grid = (get_grid(x) + flow_for_grip).permute(0, 2, 3, 1) + return F.VGG19_pytorch(x, grid, align_corners=True)